Merge pull request #2916 from chinmaygarde/master

Add: flutter build ios [--[no-]simulator]
diff --git a/dev/manual_tests/drag_and_drop.dart b/dev/manual_tests/drag_and_drop.dart
index f7c5a76..a8fd7b7 100644
--- a/dev/manual_tests/drag_and_drop.dart
+++ b/dev/manual_tests/drag_and_drop.dart
@@ -64,7 +64,7 @@
         height: config.size,
         decoration: new BoxDecoration(
           backgroundColor: config.color,
-          border: new Border.all(color: const Color(0xFF000000), width: taps.toDouble()),
+          border: new Border.all(width: taps.toDouble()),
           shape: BoxShape.circle
         ),
         child: config.child
diff --git a/examples/layers/widgets/sectors.dart b/examples/layers/widgets/sectors.dart
index 33ac584..7e5fc9f 100644
--- a/examples/layers/widgets/sectors.dart
+++ b/examples/layers/widgets/sectors.dart
@@ -134,7 +134,7 @@
           child: new Container(
             margin: new EdgeInsets.all(8.0),
             decoration: new BoxDecoration(
-              border: new Border.all(color: new Color(0xFF000000))
+              border: new Border.all()
             ),
             padding: new EdgeInsets.all(8.0),
             child: new WidgetToRenderBoxAdapter(
diff --git a/examples/material_gallery/flutter.yaml b/examples/material_gallery/flutter.yaml
index c7e9081..3e0f871 100644
--- a/examples/material_gallery/flutter.yaml
+++ b/examples/material_gallery/flutter.yaml
@@ -23,3 +23,4 @@
   - packages/flutter_gallery_assets/top_10_australian_beaches.png
   - packages/flutter_gallery_assets/jumpingjack.json
   - packages/flutter_gallery_assets/jumpingjack.png
+  - packages/flutter_gallery_assets/grain.png
diff --git a/examples/material_gallery/lib/demo/flexible_space_demo.dart b/examples/material_gallery/lib/demo/flexible_space_demo.dart
index 3a39971..762d639 100644
--- a/examples/material_gallery/lib/demo/flexible_space_demo.dart
+++ b/examples/material_gallery/lib/demo/flexible_space_demo.dart
@@ -82,7 +82,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+    final double statusBarHeight = MediaQuery.of(context).padding.top;
     return new Theme(
       data: new ThemeData(
         brightness: ThemeBrightness.light,
diff --git a/examples/material_gallery/lib/demo/list_demo.dart b/examples/material_gallery/lib/demo/list_demo.dart
index daa92ff..903226e 100644
--- a/examples/material_gallery/lib/demo/list_demo.dart
+++ b/examples/material_gallery/lib/demo/list_demo.dart
@@ -42,7 +42,7 @@
     _bottomSheet = scaffoldKey.currentState.showBottomSheet((BuildContext bottomSheetContext) {
       return new Container(
         decoration: new BoxDecoration(
-          border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+          border: new Border(top: new BorderSide(color: Colors.black26))
         ),
         child: new Column(
           mainAxisAlignment: MainAxisAlignment.collapse,
diff --git a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
index 5709593..0d11a95 100644
--- a/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
+++ b/examples/material_gallery/lib/demo/persistent_bottom_sheet_demo.dart
@@ -16,7 +16,7 @@
     Scaffold.of(context).showBottomSheet((_) {
       return new Container(
         decoration: new BoxDecoration(
-          border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+          border: new Border(top: new BorderSide(color: Colors.black26))
         ),
         child: new Padding(
           padding: const EdgeInsets.all(32.0),
diff --git a/examples/material_gallery/lib/demo/tabs_demo.dart b/examples/material_gallery/lib/demo/tabs_demo.dart
index d6ddae9..633b72d 100644
--- a/examples/material_gallery/lib/demo/tabs_demo.dart
+++ b/examples/material_gallery/lib/demo/tabs_demo.dart
@@ -35,7 +35,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+    final double statusBarHeight = MediaQuery.of(context).padding.top;
     return new TabBarSelection<_Page>(
       values: _pages,
       onChanged: (_Page value) {
diff --git a/examples/material_gallery/lib/gallery/app.dart b/examples/material_gallery/lib/gallery/app.dart
index 57a5d81..f8a0c42 100644
--- a/examples/material_gallery/lib/gallery/app.dart
+++ b/examples/material_gallery/lib/gallery/app.dart
@@ -28,10 +28,20 @@
   Widget build(BuildContext context) {
     return new MaterialApp(
       title: 'Flutter Material Gallery',
-      theme: lightTheme ? new ThemeData.light() : new ThemeData.dark(),
+      theme: lightTheme ? _kGalleryLightTheme : _kGalleryDarkTheme,
       routes: {
         '/': (BuildContext context) => new GalleryHome()
       }
     );
   }
 }
+
+ThemeData _kGalleryLightTheme = new ThemeData(
+  brightness: ThemeBrightness.light,
+  primarySwatch: Colors.purple
+);
+
+ThemeData _kGalleryDarkTheme = new ThemeData(
+  brightness: ThemeBrightness.dark,
+  primarySwatch: Colors.purple
+);
diff --git a/examples/material_gallery/lib/gallery/demo.dart b/examples/material_gallery/lib/gallery/demo.dart
deleted file mode 100644
index 0416bab..0000000
--- a/examples/material_gallery/lib/gallery/demo.dart
+++ /dev/null
@@ -1,17 +0,0 @@
-// Copyright 2016 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 'package:flutter/widgets.dart';
-
-typedef Widget GalleryDemoBuilder();
-
-class GalleryDemo {
-  GalleryDemo({ this.title, this.builder }) {
-    assert(title != null);
-    assert(builder != null);
-  }
-
-  final String title;
-  final GalleryDemoBuilder builder;
-}
diff --git a/examples/material_gallery/lib/gallery/header.dart b/examples/material_gallery/lib/gallery/header.dart
new file mode 100644
index 0000000..fcfb0dd
--- /dev/null
+++ b/examples/material_gallery/lib/gallery/header.dart
@@ -0,0 +1,189 @@
+import 'dart:async';
+
+import 'package:flutter/material.dart';
+import 'package:flutter/services.dart';
+import 'package:flutter_sprites/flutter_sprites.dart';
+
+class GalleryHeader extends StatefulWidget {
+  @override
+  _GalleryHeaderState createState() => new _GalleryHeaderState();
+}
+
+class _GalleryHeaderState extends State<GalleryHeader> {
+  _FlutterHeaderNode _headerNode;
+  ImageMap _images;
+
+  Future<Null> _loadAssets() async {
+    final AssetBundle bundle = DefaultAssetBundle.of(context);
+    _images = new ImageMap(bundle);
+    await _images.load(<String>[
+      'packages/flutter_gallery_assets/grain.png',
+    ]);
+  }
+
+  @override
+  void initState() {
+    super.initState();
+    _loadAssets().then((_) {
+      setState(() {
+        _headerNode = new _FlutterHeaderNode(_images);
+      });
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    return _headerNode == null ? new Container() : new SpriteWidget(_headerNode);
+  }
+}
+
+const Size _kCanvasSize = const Size(1024.0, 1024.0);
+const Point _kCenterPoint = const Point(512.0, 512.0);
+
+class _FlutterHeaderNode extends NodeWithSize {
+  _FlutterHeaderNode(this._images) : super(_kCanvasSize) {
+    clippingLayer.opacity = 0.0;
+    clippingLayer.actions.run(new ActionTween((double a) => clippingLayer.opacity = a, 0.0, 1.0, 0.5));
+    addChild(clippingLayer);
+
+    clippingLayer.addChild(new _BackgroundBox());
+
+    paperAnimation.position = _kCenterPoint;
+    clippingLayer.addChild(paperAnimation);
+
+    final Sprite grain = new Sprite.fromImage(_images['packages/flutter_gallery_assets/grain.png'])
+      ..position = _kCenterPoint;
+    clippingLayer.addChild(grain);
+
+    userInteractionEnabled = true;
+  }
+
+  final ImageMap _images;
+  final Layer clippingLayer = new Layer();
+  final _PaperAnimation paperAnimation = new _PaperAnimation();
+
+  @override
+  void spriteBoxPerformedLayout() {
+    clippingLayer.layerRect = spriteBox.visibleArea;
+  }
+}
+
+final List<_PaperConfig> _kPaperConfigs = <_PaperConfig>[
+  new _PaperConfig(
+    color: Colors.deepPurple[500],
+    startPosition: const Point(-300.0, -300.0),
+    startRotation: -10.0,
+    rotationSpeed: -1.0,
+    parallaxDepth: 0.0,
+    rect: new Rect.fromLTRB(-1024.0, -280.0, 1024.0, 280.0)
+  ),
+  new _PaperConfig(
+    color: Colors.purple[400],
+    startPosition: const Point(550.0, 0.0),
+    startRotation: 45.0,
+    rotationSpeed: 0.7,
+    parallaxDepth: 1.0,
+    rect: new Rect.fromLTRB(-512.0, -512.0, 512.0, 512.0)
+  ),
+  new _PaperConfig(
+    color: Colors.purple[600],
+    startPosition: const Point(550.0, 0.0),
+    startRotation: 55.0,
+    rotationSpeed: 0.9,
+    parallaxDepth: 2.0,
+    rect: new Rect.fromLTRB(-512.0, -512.0, 512.0, 512.0)
+  ),
+  new _PaperConfig(
+    color: Colors.purple[700],
+    startPosition: const Point(550.0, 0.0),
+    startRotation: 65.0,
+    rotationSpeed: 1.1,
+    parallaxDepth: 3.0,
+    rect: new Rect.fromLTRB(-512.0, -512.0, 512.0, 512.0)
+  )
+];
+
+class _PaperAnimation extends Node {
+  _PaperAnimation() {
+    for (_PaperConfig config in _kPaperConfigs) {
+
+      final _PaperSheet sheet = new _PaperSheet(config);
+      final _PaperSheetShadow shadow = new _PaperSheetShadow(config);
+
+      addChild(shadow);
+      addChild(sheet);
+      _sheets.add(sheet);
+
+      shadow.constraints = <Constraint>[
+        new ConstraintRotationToNodeRotation(sheet),
+        new ConstraintPositionToNode(sheet, offset: const Offset(0.0, 10.0))
+      ];
+    }
+  }
+
+  final List<_PaperSheet> _sheets = <_PaperSheet>[];
+}
+
+class _PaperConfig {
+  _PaperConfig({
+    this.color,
+    this.startPosition,
+    this.startRotation,
+    this.rotationSpeed,
+    this.parallaxDepth,
+    this.rect
+  });
+
+  final Color color;
+  final Point startPosition;
+  final double startRotation;
+  final double rotationSpeed;
+  final double parallaxDepth;
+  final Rect rect;
+}
+
+class _PaperSheet extends Node {
+  _PaperSheet(this._config) {
+    _paperPaint.color = _config.color;
+
+    position = _config.startPosition;
+    rotation = _config.startRotation;
+  }
+
+  final _PaperConfig _config;
+  final Paint _paperPaint = new Paint();
+
+  @override
+  void paint(Canvas canvas) {
+    canvas.drawRect(_config.rect, _paperPaint);
+  }
+
+  @override
+  void update(double dt) {
+    rotation += _config.rotationSpeed * dt;
+  }
+}
+
+class _PaperSheetShadow extends Node {
+  _PaperSheetShadow(this._config) {
+    _paperPaint.color = Colors.black45;
+    _paperPaint.maskFilter = new MaskFilter.blur(BlurStyle.normal, 10.0);
+  }
+
+  final _PaperConfig _config;
+  final Paint _paperPaint = new Paint();
+
+  @override
+  void paint(Canvas canvas) {
+    canvas.drawRect(_config.rect, _paperPaint);
+  }
+}
+
+class _BackgroundBox extends Node {
+  final Paint _boxPaint = new Paint()..color = Colors.purple[500];
+
+  @override
+  void paint(Canvas canvas) {
+    canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, _kCanvasSize.width, _kCanvasSize.height), _boxPaint);
+  }
+}
diff --git a/examples/material_gallery/lib/gallery/home.dart b/examples/material_gallery/lib/gallery/home.dart
index e71eb9d..70263e9 100644
--- a/examples/material_gallery/lib/gallery/home.dart
+++ b/examples/material_gallery/lib/gallery/home.dart
@@ -5,9 +5,9 @@
 import 'package:flutter/material.dart';
 import 'package:flutter/widgets.dart';
 
-import 'demo.dart';
 import 'drawer.dart';
-import 'section.dart';
+import 'header.dart';
+import 'item.dart';
 
 import '../demo/buttons_demo.dart';
 import '../demo/cards_demo.dart';
@@ -28,7 +28,6 @@
 import '../demo/persistent_bottom_sheet_demo.dart';
 import '../demo/progress_indicator_demo.dart';
 import '../demo/toggle_controls_demo.dart';
-import '../demo/scrolling_techniques_demo.dart';
 import '../demo/slider_demo.dart';
 import '../demo/snack_bar_demo.dart';
 import '../demo/scrollable_tabs_demo.dart';
@@ -41,6 +40,8 @@
 import '../demo/typography_demo.dart';
 import '../demo/weather_demo.dart';
 
+const double _kFlexibleSpaceMaxHeight = 256.0;
+
 class GalleryHome extends StatefulWidget {
   GalleryHome({ Key key }) : super(key: key);
 
@@ -49,109 +50,75 @@
 }
 
 class GalleryHomeState extends State<GalleryHome> {
+  final Key _scrollableKey = new UniqueKey();
+
   @override
   Widget build(BuildContext context) {
-    final double appBarHeight = 128.0;
+    final double statusBarHight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+
     return new Scaffold(
       drawer: new GalleryDrawer(),
       appBar: new AppBar(
-        expandedHeight: appBarHeight,
-        flexibleSpace: (BuildContext context) {
-          return new Container(
-            padding: const EdgeInsets.only(left: 64.0),
-            height: appBarHeight,
-            child: new Align(
-              alignment: const FractionalOffset(0.0, 1.0),
-              child: new Text('Flutter Gallery', style: Typography.white.headline)
-            )
-          );
-        }
+        expandedHeight: _kFlexibleSpaceMaxHeight,
+        flexibleSpace: (BuildContext context) =>  new FlexibleSpaceBar(
+          image: new GalleryHeader(),
+          title: new Text("Flutter Gallery")
+        )
       ),
-      body: new Block(
-        padding: const EdgeInsets.all(4.0),
-        children: <Widget>[
-          new Row(
+      scrollableKey: _scrollableKey,
+      appBarBehavior: AppBarBehavior.under,
+      body: new TwoLevelList(
+        scrollablePadding: new EdgeInsets.only(top: _kFlexibleSpaceMaxHeight + statusBarHight),
+        key: _scrollableKey,
+        type: MaterialListType.oneLine,
+        scrollableKey: _scrollableKey,
+        items: <Widget>[
+          new TwoLevelSublist(
+            leading: new Icon(icon: Icons.star),
+            title: new Text("Demos"),
             children: <Widget>[
-              new GallerySection(
-                title: 'Animation',
-                image: 'assets/section_animation.png',
-                colors: Colors.purple,
-                demos: <GalleryDemo>[
-                  new GalleryDemo(title: 'Weather', builder: () => new WeatherDemo()),
-                  new GalleryDemo(title: 'Fitness', builder: () => new FitnessDemo())
-                ]
-              ),
-              new GallerySection(
-                title: 'Style',
-                image: 'assets/section_style.png',
-                colors: Colors.green,
-                demos: <GalleryDemo>[
-                  new GalleryDemo(title: 'Colors', builder: () => new ColorsDemo()),
-                  new GalleryDemo(title: 'Typography', builder: () => new TypographyDemo())
-                ]
-              )
+              new GalleryItem(title: "Weather", builder: () => new WeatherDemo()),
+              new GalleryItem(title: "Fitness", builder: () => new FitnessDemo()),
             ]
           ),
-          new Row(
+          new TwoLevelSublist(
+            leading: new Icon(icon: Icons.extension),
+            title: new Text("Components"),
             children: <Widget>[
-              new GallerySection(
-                title: 'Layout',
-                image: 'assets/section_layout.png',
-                colors: Colors.pink
-              ),
-              new GallerySection(
-                title: 'Components',
-                image: 'assets/section_components.png',
-                colors: Colors.amber,
-                demos: <GalleryDemo>[
-                  new GalleryDemo(title: 'App Bar', builder: () => new FlexibleSpaceDemo()),
-                  new GalleryDemo(title: 'Buttons', builder: () => new ButtonsDemo()),
-                  new GalleryDemo(title: 'Buttons: Floating Action Button', builder: () => new TabsFabDemo()),
-                  new GalleryDemo(title: 'Cards', builder: () => new CardsDemo()),
-                  new GalleryDemo(title: 'Chips', builder: () => new ChipDemo()),
-                  new GalleryDemo(title: 'Date Picker', builder: () => new DatePickerDemo()),
-                  new GalleryDemo(title: 'Dialog', builder: () => new DialogDemo()),
-                  new GalleryDemo(title: 'Dropdown Button', builder: () => new DropDownDemo()),
-                  new GalleryDemo(title: 'Expand/Collapse List Control', builder: () => new TwoLevelListDemo()),
-                  new GalleryDemo(title: 'Grid', builder: () => new GridListDemo()),
-                  new GalleryDemo(title: 'Icons', builder: () => new IconsDemo()),
-                  new GalleryDemo(title: 'Leave-behind List Items', builder: () => new LeaveBehindDemo()),
-                  new GalleryDemo(title: 'List', builder: () => new ListDemo()),
-                  new GalleryDemo(title: 'Modal Bottom Sheet', builder: () => new ModalBottomSheetDemo()),
-                  new GalleryDemo(title: 'Menus', builder: () => new MenuDemo()),
-                  new GalleryDemo(title: 'Page Selector', builder: () => new PageSelectorDemo()),
-                  new GalleryDemo(title: 'Persistent Bottom Sheet', builder: () => new PersistentBottomSheetDemo()),
-                  new GalleryDemo(title: 'Progress Indicators', builder: () => new ProgressIndicatorDemo()),
-                  new GalleryDemo(title: 'Scrollable Tabs', builder: () => new ScrollableTabsDemo()),
-                  new GalleryDemo(title: 'Selection Controls', builder: () => new ToggleControlsDemo()),
-                  new GalleryDemo(title: 'Sliders', builder: () => new SliderDemo()),
-                  new GalleryDemo(title: 'SnackBar', builder: () => new SnackBarDemo()),
-                  new GalleryDemo(title: 'Tabs', builder: () => new TabsDemo()),
-                  new GalleryDemo(title: 'Text Fields', builder: () => new TextFieldDemo()),
-                  new GalleryDemo(title: 'Time Picker', builder: () => new TimePickerDemo()),
-                  new GalleryDemo(title: 'Tooltips', builder: () => new TooltipDemo())
-                ]
-              )
+              new GalleryItem(title: 'App Bar', builder: () => new FlexibleSpaceDemo()),
+              new GalleryItem(title: 'Buttons', builder: () => new ButtonsDemo()),
+              new GalleryItem(title: 'Buttons: Floating Action Button', builder: () => new TabsFabDemo()),
+              new GalleryItem(title: 'Cards', builder: () => new CardsDemo()),
+              new GalleryItem(title: 'Chips', builder: () => new ChipDemo()),
+              new GalleryItem(title: 'Date Picker', builder: () => new DatePickerDemo()),
+              new GalleryItem(title: 'Dialog', builder: () => new DialogDemo()),
+              new GalleryItem(title: 'Dropdown Button', builder: () => new DropDownDemo()),
+              new GalleryItem(title: 'Expand/Collapse List Control', builder: () => new TwoLevelListDemo()),
+              new GalleryItem(title: 'Grid', builder: () => new GridListDemo()),
+              new GalleryItem(title: 'Icons', builder: () => new IconsDemo()),
+              new GalleryItem(title: 'Leave-behind List Items', builder: () => new LeaveBehindDemo()),
+              new GalleryItem(title: 'List', builder: () => new ListDemo()),
+              new GalleryItem(title: 'Modal Bottom Sheet', builder: () => new ModalBottomSheetDemo()),
+              new GalleryItem(title: 'Menus', builder: () => new MenuDemo()),
+              new GalleryItem(title: 'Page Selector', builder: () => new PageSelectorDemo()),
+              new GalleryItem(title: 'Persistent Bottom Sheet', builder: () => new PersistentBottomSheetDemo()),
+              new GalleryItem(title: 'Progress Indicators', builder: () => new ProgressIndicatorDemo()),
+              new GalleryItem(title: 'Scrollable Tabs', builder: () => new ScrollableTabsDemo()),
+              new GalleryItem(title: 'Selection Controls', builder: () => new ToggleControlsDemo()),
+              new GalleryItem(title: 'Sliders', builder: () => new SliderDemo()),
+              new GalleryItem(title: 'SnackBar', builder: () => new SnackBarDemo()),
+              new GalleryItem(title: 'Tabs', builder: () => new TabsDemo()),
+              new GalleryItem(title: 'Text Fields', builder: () => new TextFieldDemo()),
+              new GalleryItem(title: 'Time Picker', builder: () => new TimePickerDemo()),
+              new GalleryItem(title: 'Tooltips', builder: () => new TooltipDemo()),
             ]
           ),
-          new Row(
+          new TwoLevelSublist(
+            leading: new Icon(icon: Icons.color_lens),
+            title: new Text("Style"),
             children: <Widget>[
-              new GallerySection(
-                title: 'Patterns',
-                image: 'assets/section_patterns.png',
-                colors: Colors.cyan,
-                demos: <GalleryDemo>[
-                  new GalleryDemo(title: 'Scrolling Techniques', builder: () => new ScrollingTechniquesDemo())
-                ]
-              ),
-              new GallerySection(
-                title: 'Usability',
-                image: 'assets/section_usability.png',
-                colors: Colors.lightGreen,
-                demos: <GalleryDemo>[
-                  new GalleryDemo(title: 'Tooltips', builder: () => new TooltipDemo())
-                ]
-              )
+              new GalleryItem(title: 'Colors', builder: () => new ColorsDemo()),
+              new GalleryItem(title: 'Typography', builder: () => new TypographyDemo()),
             ]
           )
         ]
diff --git a/examples/material_gallery/lib/gallery/item.dart b/examples/material_gallery/lib/gallery/item.dart
new file mode 100644
index 0000000..0651068
--- /dev/null
+++ b/examples/material_gallery/lib/gallery/item.dart
@@ -0,0 +1,28 @@
+import 'package:flutter/material.dart';
+
+typedef Widget GalleryDemoBuilder();
+
+class GalleryItem extends StatelessWidget {
+  GalleryItem({ this.title, this.icon, this.builder });
+
+  final String title;
+  final IconData icon;
+  final GalleryDemoBuilder builder;
+
+  @override
+  Widget build(BuildContext context) {
+    Widget leading = icon == null ? new Container() : new Icon(icon: icon);
+
+    return new TwoLevelListItem(
+      leading: leading,
+      title: new Text(title),
+      onTap: () {
+        if (builder != null) {
+          Navigator.push(context, new MaterialPageRoute<Null>(
+            builder: (BuildContext context) => builder()
+          ));
+        }
+      }
+    );
+  }
+}
diff --git a/examples/material_gallery/lib/gallery/section.dart b/examples/material_gallery/lib/gallery/section.dart
deleted file mode 100644
index e3d6cdd..0000000
--- a/examples/material_gallery/lib/gallery/section.dart
+++ /dev/null
@@ -1,107 +0,0 @@
-// Copyright 2016 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 'package:flutter/material.dart';
-import 'package:flutter/widgets.dart';
-
-import 'demo.dart';
-
-class GallerySection extends StatelessWidget {
-  GallerySection({ this.title, this.image, this.colors, this.demos });
-
-  final String title;
-  final String image;
-  final Map<int, Color> colors;
-  final List<GalleryDemo> demos;
-
-  void showDemo(GalleryDemo demo, BuildContext context, ThemeData theme) {
-    Navigator.push(context, new MaterialPageRoute<Null>(
-      builder: (BuildContext context) {
-        Widget child = (demo.builder == null) ? null : demo.builder();
-        return new Theme(data: theme, child: child);
-      }
-    ));
-  }
-
-  void showDemos(BuildContext context) {
-    final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
-    final ThemeData theme = new ThemeData(
-      brightness: Theme.of(context).brightness,
-      primarySwatch: colors
-    );
-    final double appBarHeight = 200.0;
-    final Key scrollableKey = new ValueKey<String>(title); // assume section titles differ
-    Navigator.push(context, new MaterialPageRoute<Null>(
-      builder: (BuildContext context) {
-        return new Theme(
-          data: theme,
-          child: new Scaffold(
-            appBarBehavior: AppBarBehavior.under,
-            scrollableKey: scrollableKey,
-            appBar: new AppBar(
-              expandedHeight: appBarHeight,
-              flexibleSpace: (BuildContext context) => new FlexibleSpaceBar(title: new Text(title))
-            ),
-            body: new Material(
-              child: new MaterialList(
-                scrollableKey: scrollableKey,
-                scrollablePadding: new EdgeInsets.only(top: appBarHeight + statusBarHeight),
-                type: MaterialListType.oneLine,
-                children: (demos ?? const <GalleryDemo>[]).map((GalleryDemo demo) {
-                  return new ListItem(
-                    title: new Text(demo.title),
-                    onTap: () { showDemo(demo, context, theme); }
-                  );
-                })
-              )
-            )
-          )
-        );
-      }
-    ));
-  }
-
-  @override
-  Widget build (BuildContext context) {
-    final ThemeData theme = new ThemeData(
-      brightness: Theme.of(context).brightness,
-      primarySwatch: colors
-    );
-    final TextStyle titleTextStyle = theme.textTheme.title.copyWith(
-      color: Colors.white
-    );
-    return new Flexible(
-      child: new GestureDetector(
-        behavior: HitTestBehavior.opaque,
-        onTap: () { showDemos(context); },
-        child: new Container(
-          height: 256.0,
-          margin: const EdgeInsets.all(4.0),
-          decoration: new BoxDecoration(backgroundColor: theme.primaryColor),
-          child: new Column(
-            children: <Widget>[
-              new Flexible(
-                child: new Padding(
-                  padding: const EdgeInsets.symmetric(horizontal: 12.0),
-                  child: new AssetImage(
-                    name: image,
-                    alignment: const FractionalOffset(0.5, 0.5),
-                    fit: ImageFit.contain
-                  )
-                )
-              ),
-              new Padding(
-                padding: const EdgeInsets.all(16.0),
-                child: new Align(
-                  alignment: const FractionalOffset(0.0, 1.0),
-                  child: new Text(title, style: titleTextStyle)
-                )
-              )
-            ]
-          )
-        )
-      )
-    );
-  }
-}
diff --git a/examples/material_gallery/pubspec.yaml b/examples/material_gallery/pubspec.yaml
index 06c5b03..158ebf7 100644
--- a/examples/material_gallery/pubspec.yaml
+++ b/examples/material_gallery/pubspec.yaml
@@ -7,4 +7,4 @@
     path: ../../packages/flutter
   flutter_sprites:
     path: ../../packages/flutter_sprites
-  flutter_gallery_assets: '0.0.9'
+  flutter_gallery_assets: '0.0.12'
diff --git a/examples/stocks/flutter.yaml b/examples/stocks/flutter.yaml
index ee90ca7..c2cedb4 100644
--- a/examples/stocks/flutter.yaml
+++ b/examples/stocks/flutter.yaml
@@ -1,4 +1,2 @@
 name: stocks
-version: 0.0.2
-update-url: http://localhost:9888/
 uses-material-design: true
diff --git a/examples/stocks/lib/stock_symbol_viewer.dart b/examples/stocks/lib/stock_symbol_viewer.dart
index 5f2ff07..6812fed 100644
--- a/examples/stocks/lib/stock_symbol_viewer.dart
+++ b/examples/stocks/lib/stock_symbol_viewer.dart
@@ -100,7 +100,7 @@
     return new Container(
       padding: new EdgeInsets.all(10.0),
       decoration: new BoxDecoration(
-        border: new Border(top: new BorderSide(color: Colors.black26, width: 1.0))
+        border: new Border(top: new BorderSide(color: Colors.black26))
       ),
       child: new StockSymbolView(stock: stock)
    );
diff --git a/packages/flutter/lib/src/http/mojo_client.dart b/packages/flutter/lib/src/http/mojo_client.dart
index 3665f42..af1d8f0 100644
--- a/packages/flutter/lib/src/http/mojo_client.dart
+++ b/packages/flutter/lib/src/http/mojo_client.dart
@@ -149,8 +149,14 @@
       ByteData data = await mojo.DataPipeDrainer.drainHandle(response.body);
       Uint8List bodyBytes = new Uint8List.view(data.buffer);
       return new Response(bodyBytes: bodyBytes, statusCode: response.statusCode);
-    } catch (e) {
-      print("NetworkService unavailable $e");
+    } catch (exception) {
+      assert(() {
+        debugPrint('-- EXCEPTION CAUGHT BY NETWORKING HTTP LIBRARY -------------------------');
+        debugPrint('An exception was raised while sending bytes to the Mojo network library:');
+        debugPrint('$exception');
+        debugPrint('------------------------------------------------------------------------');
+        return true;
+      });
       return new Response(statusCode: 500);
     } finally {
       loader.close();
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index 936ae25..40c76eb 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -110,7 +110,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+    final double statusBarHeight = MediaQuery.of(context).padding.top;
     final ThemeData theme = Theme.of(context);
 
     IconThemeData iconTheme = theme.primaryIconTheme;
@@ -134,16 +134,18 @@
     }
 
     final List<Widget> toolBarRow = <Widget>[];
-    if (leading != null)
-      toolBarRow.add(leading);
-    toolBarRow.add(
-      new Flexible(
-        child: new Padding(
-          padding: new EdgeInsets.only(left: 24.0),
-          child: title != null ? new DefaultTextStyle(style: centerStyle, child: title) : null
-        )
+    if (leading != null) {
+      toolBarRow.add(new Padding(
+        padding: new EdgeInsets.only(right: 16.0),
+        child: leading
+      ));
+    }
+    toolBarRow.add(new Flexible(
+      child: new Padding(
+        padding: new EdgeInsets.only(left: 8.0),
+        child: title != null ? new DefaultTextStyle(style: centerStyle, child: title) : null
       )
-    );
+    ));
     if (actions != null)
       toolBarRow.addAll(actions);
 
diff --git a/packages/flutter/lib/src/material/drawer_header.dart b/packages/flutter/lib/src/material/drawer_header.dart
index f74efeb..95dab00 100644
--- a/packages/flutter/lib/src/material/drawer_header.dart
+++ b/packages/flutter/lib/src/material/drawer_header.dart
@@ -20,7 +20,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterial(context));
-    final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+    final double statusBarHeight = MediaQuery.of(context).padding.top;
     return new Container(
       height: statusBarHeight + kMaterialDrawerHeight,
       decoration: new BoxDecoration(
diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart
index a9cd896..28f7b71 100644
--- a/packages/flutter/lib/src/material/flexible_space_bar.dart
+++ b/packages/flutter/lib/src/material/flexible_space_bar.dart
@@ -25,7 +25,7 @@
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasScaffold(context));
-    final double statusBarHeight = (MediaQuery.of(context)?.padding ?? EdgeInsets.zero).top;
+    final double statusBarHeight = MediaQuery.of(context).padding.top;
     final Animation<double> animation = Scaffold.of(context).appBarAnimation;
     final double appBarHeight = Scaffold.of(context).appBarHeight + statusBarHeight;
     final double toolBarHeight = kToolBarHeight + statusBarHeight;
diff --git a/packages/flutter/lib/src/material/scaffold.dart b/packages/flutter/lib/src/material/scaffold.dart
index b1bfaa9..1d248de 100644
--- a/packages/flutter/lib/src/material/scaffold.dart
+++ b/packages/flutter/lib/src/material/scaffold.dart
@@ -453,7 +453,7 @@
   }
 
   Widget _buildScrollableAppBar(BuildContext context) {
-    final EdgeInsets padding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
+    final EdgeInsets padding = MediaQuery.of(context).padding;
     final double expandedHeight = (config.appBar?.expandedHeight ?? 0.0) + padding.top;
     final double collapsedHeight = (config.appBar?.collapsedHeight ?? 0.0) + padding.top;
     final double minimumHeight = (config.appBar?.minimumHeight ?? 0.0) + padding.top;
@@ -500,7 +500,7 @@
 
   @override
   Widget build(BuildContext context) {
-    final EdgeInsets padding = MediaQuery.of(context)?.padding ?? EdgeInsets.zero;
+    final EdgeInsets padding = MediaQuery.of(context).padding;
 
     if (_snackBars.length > 0) {
       final ModalRoute<dynamic> route = ModalRoute.of(context);
diff --git a/packages/flutter/lib/src/material/two_level_list.dart b/packages/flutter/lib/src/material/two_level_list.dart
index 90301fd..a242976 100644
--- a/packages/flutter/lib/src/material/two_level_list.dart
+++ b/packages/flutter/lib/src/material/two_level_list.dart
@@ -154,14 +154,25 @@
 }
 
 class TwoLevelList extends StatelessWidget {
-  TwoLevelList({ Key key, this.scrollableKey, this.items, this.type: MaterialListType.twoLine }) : super(key: key);
+  TwoLevelList({
+    Key key,
+    this.scrollableKey,
+    this.items,
+    this.type: MaterialListType.twoLine,
+    this.scrollablePadding
+  }) : super(key: key);
 
   final List<Widget> items;
   final MaterialListType type;
   final Key scrollableKey;
+  final EdgeInsets scrollablePadding;
 
   @override
   Widget build(BuildContext context) {
-    return new Block(children: KeyedSubtree.ensureUniqueKeysForList(items), scrollableKey: scrollableKey);
+    return new Block(
+      padding: scrollablePadding,
+      children: KeyedSubtree.ensureUniqueKeysForList(items),
+      scrollableKey: scrollableKey
+    );
   }
 }
diff --git a/packages/flutter/lib/src/painting/box_painter.dart b/packages/flutter/lib/src/painting/box_painter.dart
index 651cefb..97cbb6a 100644
--- a/packages/flutter/lib/src/painting/box_painter.dart
+++ b/packages/flutter/lib/src/painting/box_painter.dart
@@ -25,30 +25,52 @@
   return borderRadius > shortestSide ? shortestSide : borderRadius;
 }
 
+/// The style of line to draw for a [BorderSide] in a [Border].
+enum BorderStyle {
+  /// Skip the border.
+  none,
+
+  /// Draw the border as a solid line.
+  solid,
+
+  // if you add more, think about how they will lerp
+}
+
 /// A side of a border of a box.
 class BorderSide {
   const BorderSide({
     this.color: const Color(0xFF000000),
-    this.width: 1.0
+    this.width: 1.0,
+    this.style: BorderStyle.solid
   });
 
   /// The color of this side of the border.
   final Color color;
 
-  /// The width of this side of the border.
+  /// The width of this side of the border, in logical pixels. A
+  /// zero-width border is a hairline border. To omit the border
+  /// entirely, set the [style] to [BorderStyle.none].
   final double width;
 
-  /// A black border side of zero width.
-  static const BorderSide none = const BorderSide(width: 0.0);
+  /// The style of this side of the border.
+  ///
+  /// To omit a side, set [style] to [BorderStyle.none]. This skips
+  /// painting the border, but the border still has a [width].
+  final BorderStyle style;
+
+  /// A hairline black border that is not rendered.
+  static const BorderSide none = const BorderSide(width: 0.0, style: BorderStyle.none);
 
   /// Creates a copy of this border but with the given fields replaced with the new values.
   BorderSide copyWith({
     Color color,
-    double width
+    double width,
+    BorderStyle style
   }) {
     return new BorderSide(
       color: color ?? this.color,
-      width: width ?? this.width
+      width: width ?? this.width,
+      style: style ?? this.style
     );
   }
 
@@ -56,9 +78,38 @@
   static BorderSide lerp(BorderSide a, BorderSide b, double t) {
     assert(a != null);
     assert(b != null);
+    if (t == 0.0)
+      return a;
+    if (t == 1.0)
+      return b;
+    if (a.style == b.style) {
+      return new BorderSide(
+        color: Color.lerp(a.color, b.color, t),
+        width: ui.lerpDouble(a.width, b.width, t),
+        style: a.style // == b.style
+      );
+    }
+    Color colorA, colorB;
+    switch (a.style) {
+      case BorderStyle.solid:
+        colorA = a.color;
+        break;
+      case BorderStyle.none:
+        colorA = a.color.withAlpha(0x00);
+        break;
+    }
+    switch (b.style) {
+      case BorderStyle.solid:
+        colorB = b.color;
+        break;
+      case BorderStyle.none:
+        colorB = b.color.withAlpha(0x00);
+        break;
+    }
     return new BorderSide(
-      color: Color.lerp(a.color, b.color, t),
-      width: ui.lerpDouble(a.width, b.width, t)
+      color: Color.lerp(colorA, colorB, t),
+      width: ui.lerpDouble(a.width, b.width, t),
+      style: BorderStyle.solid
     );
   }
 
@@ -70,14 +121,15 @@
       return false;
     final BorderSide typedOther = other;
     return color == typedOther.color &&
-           width == typedOther.width;
+           width == typedOther.width &&
+           style == typedOther.style;
   }
 
   @override
-  int get hashCode => hashValues(color, width);
+  int get hashCode => hashValues(color, width, style);
 
   @override
-  String toString() => 'BorderSide($color, $width)';
+  String toString() => 'BorderSide($color, $width, $style)';
 }
 
 /// A border of a box, comprised of four sides.
@@ -92,9 +144,10 @@
   /// A uniform border with all sides the same color and width.
   factory Border.all({
     Color color: const Color(0xFF000000),
-    double width: 1.0
+    double width: 1.0,
+    BorderStyle style: BorderStyle.solid
   }) {
-    final BorderSide side = new BorderSide(color: color, width: width);
+    final BorderSide side = new BorderSide(color: color, width: width, style: style);
     return new Border(top: side, right: side, bottom: side, left: side);
   }
 
@@ -134,6 +187,12 @@
         left.width != topWidth)
       return false;
 
+    final BorderStyle topStyle = top.style;
+    if (right.style != topStyle ||
+        bottom.style != topStyle ||
+        left.style != topStyle)
+      return false;
+
     return true;
   }
 
@@ -186,57 +245,99 @@
     assert(bottom != null);
     assert(left != null);
 
-    Paint paint = new Paint();
+    Paint paint = new Paint()
+      ..strokeWidth = 0.0; // used for hairline borders
     Path path;
 
-    // TODO(ianh): Handle hairline border by drawing a single line instead of a wedge
+    switch (top.style) {
+      case BorderStyle.solid:
+        paint.color = top.color;
+        path = new Path();
+        path.moveTo(rect.left, rect.top);
+        path.lineTo(rect.right, rect.top);
+        if (top.width == 0.0) {
+          paint.style = PaintingStyle.stroke;
+        } else {
+          paint.style = PaintingStyle.fill;
+          path.lineTo(rect.right - right.width, rect.top + top.width);
+          path.lineTo(rect.left + left.width, rect.top + top.width);
+        }
+        canvas.drawPath(path, paint);
+        break;
+      case BorderStyle.none: ;
+    }
 
-    paint.color = top.color;
-    path = new Path();
-    path.moveTo(rect.left, rect.top);
-    path.lineTo(rect.left + left.width, rect.top + top.width);
-    path.lineTo(rect.right - right.width, rect.top + top.width);
-    path.lineTo(rect.right, rect.top);
-    path.close();
-    canvas.drawPath(path, paint);
+    switch (right.style) {
+      case BorderStyle.solid:
+        paint.color = right.color;
+        path = new Path();
+        path.moveTo(rect.right, rect.top);
+        path.lineTo(rect.right, rect.bottom);
+        if (right.width == 0.0) {
+          paint.style = PaintingStyle.stroke;
+        } else {
+          paint.style = PaintingStyle.fill;
+          path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
+          path.lineTo(rect.right - right.width, rect.top + top.width);
+        }
+        canvas.drawPath(path, paint);
+        break;
+      case BorderStyle.none: ;
+    }
 
-    paint.color = right.color;
-    path = new Path();
-    path.moveTo(rect.right, rect.top);
-    path.lineTo(rect.right - right.width, rect.top + top.width);
-    path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
-    path.lineTo(rect.right, rect.bottom);
-    path.close();
-    canvas.drawPath(path, paint);
+    switch (bottom.style) {
+      case BorderStyle.solid:
+        paint.color = bottom.color;
+        path = new Path();
+        path.moveTo(rect.right, rect.bottom);
+        path.lineTo(rect.left, rect.bottom);
+        if (bottom.width == 0.0) {
+          paint.style = PaintingStyle.stroke;
+        } else {
+          paint.style = PaintingStyle.fill;
+          path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
+          path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
+        }
+        canvas.drawPath(path, paint);
+        break;
+      case BorderStyle.none: ;
+    }
 
-    paint.color = bottom.color;
-    path = new Path();
-    path.moveTo(rect.right, rect.bottom);
-    path.lineTo(rect.right - right.width, rect.bottom - bottom.width);
-    path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
-    path.lineTo(rect.left, rect.bottom);
-    path.close();
-    canvas.drawPath(path, paint);
-
-    paint.color = left.color;
-    path = new Path();
-    path.moveTo(rect.left, rect.bottom);
-    path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
-    path.lineTo(rect.left + left.width, rect.top + top.width);
-    path.lineTo(rect.left, rect.top);
-    path.close();
-    canvas.drawPath(path, paint);
+    switch (left.style) {
+      case BorderStyle.solid:
+        paint.color = left.color;
+        path = new Path();
+        path.moveTo(rect.left, rect.bottom);
+        path.lineTo(rect.left, rect.top);
+        if (right.width == 0.0) {
+          paint.style = PaintingStyle.stroke;
+        } else {
+          paint.style = PaintingStyle.fill;
+          path.lineTo(rect.left + left.width, rect.top + top.width);
+          path.lineTo(rect.left + left.width, rect.bottom - bottom.width);
+        }
+        canvas.drawPath(path, paint);
+        break;
+      case BorderStyle.none: ;
+    }
   }
 
   void _paintBorderWithRadius(Canvas canvas, Rect rect, double borderRadius) {
     assert(isUniform);
-    Color color = top.color;
-    double width = top.width;
+    Paint paint = new Paint()
+      ..color = top.color;
     double radius = _getEffectiveBorderRadius(rect, borderRadius);
-    // TODO(ianh): Handle hairline borders by just drawing an RRect instead
     RRect outer = new RRect.fromRectXY(rect, radius, radius);
-    RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
-    canvas.drawDRRect(outer, inner, new Paint()..color = color);
+    double width = top.width;
+    if (width == 0.0) {
+      paint
+        ..style = PaintingStyle.stroke
+        ..strokeWidth = 0.0;
+      canvas.drawRRect(outer, paint);
+    } else {
+      RRect inner = new RRect.fromRectXY(rect.deflate(width), radius - width, radius - width);
+      canvas.drawDRRect(outer, inner, paint);
+    }
   }
 
   void _paintBorderWithCircle(Canvas canvas, Rect rect) {
@@ -568,6 +669,14 @@
   /// As small as possible while still covering the entire box.
   cover,
 
+  /// Make sure the full width of the image is shown, regardless of
+  /// whether this means the image overflows the box vertically.
+  fitWidth,
+
+  /// Make sure the full height of the image is shown, regardless of
+  /// whether this means the image overflows the box horizontally.
+  fitHeight,
+
   /// Center the image within the box and discard any portions of the image that
   /// lie outside the box.
   none,
@@ -672,6 +781,16 @@
       }
       destinationSize = outputSize;
       break;
+    case ImageFit.fitWidth:
+      sourceSize = new Size(inputSize.width, inputSize.width * outputSize.height / outputSize.width);
+      sourcePosition = new Point(0.0, (inputSize.height - sourceSize.height) * (alignment?.dy ?? 0.5));
+      destinationSize = new Size(outputSize.width, sourceSize.height * outputSize.width / sourceSize.width);
+      break;
+    case ImageFit.fitHeight:
+      sourceSize = new Size(inputSize.height * outputSize.width / outputSize.height, inputSize.height);
+      sourcePosition = new Point((inputSize.width - sourceSize.width) * (alignment?.dx ?? 0.5), 0.0);
+      destinationSize = new Size(sourceSize.width * outputSize.height / sourceSize.height, outputSize.height);
+      break;
     case ImageFit.none:
       sourceSize = new Size(math.min(inputSize.width, outputSize.width),
                             math.min(inputSize.height, outputSize.height));
diff --git a/packages/flutter/lib/src/painting/edge_insets.dart b/packages/flutter/lib/src/painting/edge_insets.dart
index 619b67c..3749faa 100644
--- a/packages/flutter/lib/src/painting/edge_insets.dart
+++ b/packages/flutter/lib/src/painting/edge_insets.dart
@@ -2,7 +2,7 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
-import 'dart:ui' as ui show lerpDouble;
+import 'dart:ui' as ui show lerpDouble, WindowPadding;
 
 import 'basic_types.dart';
 
@@ -23,20 +23,26 @@
 
   /// Constructs insets where all the offsets are value.
   const EdgeInsets.all(double value)
-      : top = value, right = value, bottom = value, left = value;
+      : left = value, top = value, right = value, bottom = value;
 
   /// Constructs insets with only the given values non-zero.
   const EdgeInsets.only({
+    this.left: 0.0,
     this.top: 0.0,
     this.right: 0.0,
-    this.bottom: 0.0,
-    this.left: 0.0
+    this.bottom: 0.0
   });
 
   /// Constructs insets with symmetrical vertical and horizontal offsets.
   const EdgeInsets.symmetric({ double vertical: 0.0,
                              double horizontal: 0.0 })
-    : top = vertical, left = horizontal, bottom = vertical, right = horizontal;
+    : left = horizontal, top = vertical, right = horizontal, bottom = vertical;
+
+  EdgeInsets.fromWindowPadding(ui.WindowPadding padding)
+    : left = padding.left, top = padding.top, right = padding.right, bottom = padding.bottom;
+
+  /// The offset from the left.
+  final double left;
 
   /// The offset from the top.
   final double top;
@@ -47,11 +53,8 @@
   /// The offset from the bottom.
   final double bottom;
 
-  /// The offset from the left.
-  final double left;
-
   /// Whether every dimension is non-negative.
-  bool get isNonNegative => top >= 0.0 && right >= 0.0 && bottom >= 0.0 && left >= 0.0;
+  bool get isNonNegative => left >= 0.0 && top >= 0.0 && right >= 0.0 && bottom >= 0.0;
 
   /// The total offset in the vertical direction.
   double get horizontal => left + right;
@@ -151,15 +154,15 @@
     if (other is! EdgeInsets)
       return false;
     final EdgeInsets typedOther = other;
-    return top == typedOther.top &&
+    return left == typedOther.left &&
+           top == typedOther.top &&
            right == typedOther.right &&
-           bottom == typedOther.bottom &&
-           left == typedOther.left;
+           bottom == typedOther.bottom;
   }
 
   @override
-  int get hashCode => hashValues(top, left, bottom, right);
+  int get hashCode => hashValues(left, top, right, bottom);
 
   @override
-  String toString() => "EdgeInsets($top, $right, $bottom, $left)";
+  String toString() => "EdgeInsets($left, $top, $right, $bottom)";
 }
diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart
index 9e2fae3..b44badd 100644
--- a/packages/flutter/lib/src/rendering/box.dart
+++ b/packages/flutter/lib/src/rendering/box.dart
@@ -410,7 +410,7 @@
 
 /// Parent data used by [RenderBox] and its subclasses.
 class BoxParentData extends ParentData {
-  /// The offset at which to paint the child in the parent's coordinate system
+  /// The offset at which to paint the child in the parent's coordinate system.
   Offset offset = Offset.zero;
 
   @override
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index 2b946dc..104e4a1 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -567,11 +567,14 @@
 /// This class paints its child into an intermediate buffer and then blends the
 /// child back into the scene partially transparent.
 ///
-/// This class is relatively expensive because it requires painting the child
-/// into an intermediate buffer.
+/// For values of opacity other than 0.0 and 1.0, this class is relatively
+/// expensive because it requires painting the child into an intermediate
+/// buffer. For the value 0.0, the child is simply not painted at all. For the
+/// value 1.0, the child is painted immediately without an intermediate buffer.
 class RenderOpacity extends RenderProxyBox {
   RenderOpacity({ RenderBox child, double opacity: 1.0 })
     : _opacity = opacity, _alpha = _getAlphaFromOpacity(opacity), super(child) {
+    assert(opacity != null);
     assert(opacity >= 0.0 && opacity <= 1.0);
   }
 
@@ -582,6 +585,12 @@
   ///
   /// An opacity of 1.0 is fully opaque. An opacity of 0.0 is fully transparent
   /// (i.e., invisible).
+  ///
+  /// The opacity must not be null.
+  ///
+  /// Values 1.0 and 0.0 are painted with a fast path. Other values
+  /// require painting the child into an intermediate buffer, which is
+  /// expensive.
   double get opacity => _opacity;
   double _opacity;
   void set opacity (double newOpacity) {
diff --git a/packages/flutter/lib/src/services/fetch.dart b/packages/flutter/lib/src/services/fetch.dart
index fbe8b59..bf2f7fc 100644
--- a/packages/flutter/lib/src/services/fetch.dart
+++ b/packages/flutter/lib/src/services/fetch.dart
@@ -4,32 +4,47 @@
 
 import 'dart:async';
 
-import 'package:mojo/mojo/url_request.mojom.dart';
-import 'package:mojo/mojo/url_response.mojom.dart';
-import 'package:mojo_services/mojo/url_loader.mojom.dart';
+import 'package:mojo/mojo/url_request.mojom.dart' as mojom;
+import 'package:mojo/mojo/url_response.mojom.dart' as mojom;
+import 'package:mojo_services/mojo/url_loader.mojom.dart' as mojom;
 
 import '../http/mojo_client.dart';
+import 'print.dart';
 
 export 'package:mojo/mojo/url_response.mojom.dart' show UrlResponse;
 
-Future<UrlResponse> fetch(UrlRequest request) async {
-  UrlLoaderProxy loader = new UrlLoaderProxy.unbound();
+Future<mojom.UrlResponse> fetch(mojom.UrlRequest request, { bool require200: false }) async {
+  mojom.UrlLoaderProxy loader = new mojom.UrlLoaderProxy.unbound();
   try {
     MojoClient.networkService.ptr.createUrlLoader(loader);
-    UrlResponse response = (await loader.ptr.start(request)).response;
+    mojom.UrlResponse response = (await loader.ptr.start(request)).response;
+    if (require200 && (response.error != null || response.statusCode != 200)) {
+      StringBuffer message = new StringBuffer();
+      message.writeln('Could not ${request.method ?? "fetch"} ${request.url ?? "resource"}');
+      if (response.error != null)
+        message.writeln('Network error: ${response.error.code} ${response.error.description ?? "<unknown network error>"}');
+      if (response.statusCode != 200)
+        message.writeln('Protocol error: ${response.statusCode} ${response.statusLine ?? "<no server message>"}');
+      if (response.url != request.url)
+        message.writeln('Final URL after redirects was: ${response.url}');
+      throw message;
+    }
     return response;
-  } catch (e) {
-    print("NetworkService unavailable $e");
-    return new UrlResponse()..statusCode = 500;
+  } catch (exception) {
+    debugPrint('-- EXCEPTION CAUGHT BY NETWORKING HTTP LIBRARY -------------------------');
+    debugPrint('An exception was raised while sending bytes to the Mojo network library:');
+    debugPrint('$exception');
+    debugPrint('------------------------------------------------------------------------');
+    return null;
   } finally {
     loader.close();
   }
 }
 
-Future<UrlResponse> fetchUrl(String relativeUrl) {
+Future<mojom.UrlResponse> fetchUrl(String relativeUrl, { bool require200: false }) {
   String url = Uri.base.resolve(relativeUrl).toString();
-  UrlRequest request = new UrlRequest()
+  mojom.UrlRequest request = new mojom.UrlRequest()
     ..url = url
     ..autoFollowRedirects = true;
-  return fetch(request);
+  return fetch(request, require200: require200);
 }
diff --git a/packages/flutter/lib/src/services/image_cache.dart b/packages/flutter/lib/src/services/image_cache.dart
index 4141210..560e182 100644
--- a/packages/flutter/lib/src/services/image_cache.dart
+++ b/packages/flutter/lib/src/services/image_cache.dart
@@ -52,15 +52,14 @@
 
   @override
   Future<ImageInfo> loadImage() async {
-    UrlResponse response = await fetchUrl(_url);
-    if (response.statusCode >= 400) {
-      print("Failed (${response.statusCode}) to load image $_url");
-      return null;
+    UrlResponse response = await fetchUrl(_url, require200: true);
+    if (response != null) {
+      return new ImageInfo(
+        image: await decodeImageFromDataPipe(response.body),
+        scale: _scale
+      );
     }
-    return new ImageInfo(
-      image: await decodeImageFromDataPipe(response.body),
-      scale: _scale
-    );
+    return null;
   }
 
   @override
diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 30d436a..5abee0c 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -104,10 +104,6 @@
   WidgetsAppState<WidgetsApp> createState() => new WidgetsAppState<WidgetsApp>();
 }
 
-EdgeInsets _getPadding(ui.WindowPadding padding) {
-  return new EdgeInsets.fromLTRB(padding.left, padding.top, padding.right, padding.bottom);
-}
-
 class WidgetsAppState<T extends WidgetsApp> extends State<T> implements BindingObserver {
 
   GlobalObjectKey _navigator;
@@ -173,11 +169,7 @@
     }
 
     Widget result = new MediaQuery(
-      data: new MediaQueryData(
-        size: ui.window.size,
-        devicePixelRatio: ui.window.devicePixelRatio,
-        padding: _getPadding(ui.window.padding)
-      ),
+      data: new MediaQueryData.fromWindow(ui.window),
       child: new LocaleQuery(
         data: _localeData,
         child: new AssetVendor(
diff --git a/packages/flutter/lib/src/widgets/child_view.dart b/packages/flutter/lib/src/widgets/child_view.dart
index 4e43bee..9b19630 100644
--- a/packages/flutter/lib/src/widgets/child_view.dart
+++ b/packages/flutter/lib/src/widgets/child_view.dart
@@ -4,7 +4,6 @@
 
 import 'package:flutter/rendering.dart';
 
-import 'debug.dart';
 import 'framework.dart';
 import 'media_query.dart';
 
@@ -17,7 +16,6 @@
 
   @override
   Widget build(BuildContext context) {
-    assert(debugCheckHasMediaQuery(context));
     return new _ChildViewWidget(
       child: child,
       scale: MediaQuery.of(context).devicePixelRatio
diff --git a/packages/flutter/lib/src/widgets/debug.dart b/packages/flutter/lib/src/widgets/debug.dart
index ac290ad..4e52d03 100644
--- a/packages/flutter/lib/src/widgets/debug.dart
+++ b/packages/flutter/lib/src/widgets/debug.dart
@@ -5,25 +5,6 @@
 import 'dart:collection';
 
 import 'framework.dart';
-import 'media_query.dart';
-
-bool debugCheckHasMediaQuery(BuildContext context) {
-  assert(() {
-    if (MediaQuery.of(context) == null) {
-      Element element = context;
-      throw new FlutterError(
-        'No MediaQuery widget found.\n'
-        '${element.widget.runtimeType} widgets require a MediaQuery widget ancestor.\n'
-        'The specific widget that could not find a MediaQuery ancestor was:\n'
-        '  ${element.widget}'
-        'The ownership chain for the affected widget is:\n'
-        '  ${element.debugGetOwnershipChain(10)}'
-      );
-    }
-    return true;
-  });
-  return true;
-}
 
 Key _firstNonUniqueKey(Iterable<Widget> widgets) {
   Set<Key> keySet = new HashSet<Key>();
diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart
index 1d8fe0b..1332da2 100644
--- a/packages/flutter/lib/src/widgets/drag_target.dart
+++ b/packages/flutter/lib/src/widgets/drag_target.dart
@@ -15,6 +15,9 @@
 typedef void DragTargetAccept<T>(T data);
 typedef Widget DragTargetBuilder<T>(BuildContext context, List<T> candidateData, List<dynamic> rejectedData);
 
+/// Called when a [Draggable] is dropped without being accepted by a [DragTarget].
+typedef void OnDraggableCanceled(Velocity velocity, Offset offset);
+
 /// Where the [Draggable] should be anchored during a drag.
 enum DragAnchor {
   /// Display the feedback anchored at the position of the original child. If
@@ -45,7 +48,8 @@
     this.feedback,
     this.feedbackOffset: Offset.zero,
     this.dragAnchor: DragAnchor.child,
-    this.maxSimultaneousDrags
+    this.maxSimultaneousDrags,
+    this.onDraggableCanceled
   }) : super(key: key) {
     assert(child != null);
     assert(feedback != null);
@@ -80,6 +84,9 @@
   /// dragged at a time.
   final int maxSimultaneousDrags;
 
+  /// Called when the draggable is dropped without being accepted by a [DragTarget].
+  final OnDraggableCanceled onDraggableCanceled;
+
   /// Should return a new MultiDragGestureRecognizer instance
   /// constructed with the given arguments.
   MultiDragGestureRecognizer<MultiDragPointerState> createRecognizer(GestureMultiDragStartCallback onStart);
@@ -98,7 +105,8 @@
     Widget feedback,
     Offset feedbackOffset: Offset.zero,
     DragAnchor dragAnchor: DragAnchor.child,
-    int maxSimultaneousDrags
+    int maxSimultaneousDrags,
+    OnDraggableCanceled onDraggableCanceled
   }) : super(
     key: key,
     data: data,
@@ -107,7 +115,8 @@
     feedback: feedback,
     feedbackOffset: feedbackOffset,
     dragAnchor: dragAnchor,
-    maxSimultaneousDrags: maxSimultaneousDrags
+    maxSimultaneousDrags: maxSimultaneousDrags,
+    onDraggableCanceled: onDraggableCanceled
   );
 
   @override
@@ -127,7 +136,8 @@
     Widget feedback,
     Offset feedbackOffset: Offset.zero,
     DragAnchor dragAnchor: DragAnchor.child,
-    int maxSimultaneousDrags
+    int maxSimultaneousDrags,
+    OnDraggableCanceled onDraggableCanceled
   }) : super(
     key: key,
     data: data,
@@ -136,7 +146,8 @@
     feedback: feedback,
     feedbackOffset: feedbackOffset,
     dragAnchor: dragAnchor,
-    maxSimultaneousDrags: maxSimultaneousDrags
+    maxSimultaneousDrags: maxSimultaneousDrags,
+    onDraggableCanceled: onDraggableCanceled
   );
 
   @override
@@ -156,7 +167,8 @@
     Widget feedback,
     Offset feedbackOffset: Offset.zero,
     DragAnchor dragAnchor: DragAnchor.child,
-    int maxSimultaneousDrags
+    int maxSimultaneousDrags,
+    OnDraggableCanceled onDraggableCanceled
   }) : super(
     key: key,
     data: data,
@@ -165,7 +177,8 @@
     feedback: feedback,
     feedbackOffset: feedbackOffset,
     dragAnchor: dragAnchor,
-    maxSimultaneousDrags: maxSimultaneousDrags
+    maxSimultaneousDrags: maxSimultaneousDrags,
+    onDraggableCanceled: onDraggableCanceled
   );
 
   @override
@@ -184,7 +197,8 @@
     Widget feedback,
     Offset feedbackOffset: Offset.zero,
     DragAnchor dragAnchor: DragAnchor.child,
-    int maxSimultaneousDrags
+    int maxSimultaneousDrags,
+    OnDraggableCanceled onDraggableCanceled
   }) : super(
     key: key,
     data: data,
@@ -193,7 +207,8 @@
     feedback: feedback,
     feedbackOffset: feedbackOffset,
     dragAnchor: dragAnchor,
-    maxSimultaneousDrags: maxSimultaneousDrags
+    maxSimultaneousDrags: maxSimultaneousDrags,
+    onDraggableCanceled: onDraggableCanceled
   );
 
   @override
@@ -248,9 +263,11 @@
       dragStartPoint: dragStartPoint,
       feedback: config.feedback,
       feedbackOffset: config.feedbackOffset,
-      onDragEnd: () {
+      onDragEnd: (Velocity velocity, Offset offset, bool wasAccepted) {
         setState(() {
           _activeCount -= 1;
+          if (!wasAccepted && config.onDraggableCanceled != null)
+            config.onDraggableCanceled(velocity, offset);
         });
       }
     );
@@ -341,6 +358,7 @@
 
 
 enum _DragEndKind { dropped, canceled }
+typedef void _OnDragEnd(Velocity velocity, Offset offset, bool wasAccepted);
 
 // The lifetime of this object is a little dubious right now. Specifically, it
 // lives as long as the pointer is down. Arguably it should self-immolate if the
@@ -370,7 +388,7 @@
   final Point dragStartPoint;
   final Widget feedback;
   final Offset feedbackOffset;
-  final VoidCallback onDragEnd;
+  final _OnDragEnd onDragEnd;
 
   _DragTargetState<T> _activeTarget;
   bool _activeTargetWillAcceptDrop = false;
@@ -387,7 +405,7 @@
 
   @override
   void end(Velocity velocity) {
-    finish(_DragEndKind.dropped);
+    finish(_DragEndKind.dropped, velocity);
   }
 
   @override
@@ -422,19 +440,23 @@
     return null;
   }
 
-  void finish(_DragEndKind endKind) {
+  void finish(_DragEndKind endKind, [Velocity velocity]) {
+    bool wasAccepted = false;
     if (_activeTarget != null) {
-      if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop)
+      if (endKind == _DragEndKind.dropped && _activeTargetWillAcceptDrop) {
         _activeTarget.didDrop(data);
-      else
+        wasAccepted = true;
+      } else {
         _activeTarget.didLeave(data);
+      }
     }
     _activeTarget = null;
     _activeTargetWillAcceptDrop = false;
     _entry.remove();
     _entry = null;
+    // TODO(ianh): consider passing _entry as well so the client can perform an animation.
     if (onDragEnd != null)
-      onDragEnd();
+      onDragEnd(velocity ?? Velocity.zero, _lastOffset, wasAccepted);
   }
 
   Widget _build(BuildContext context) {
diff --git a/packages/flutter/lib/src/widgets/focus.dart b/packages/flutter/lib/src/widgets/focus.dart
index ef82659..79f3130 100644
--- a/packages/flutter/lib/src/widgets/focus.dart
+++ b/packages/flutter/lib/src/widgets/focus.dart
@@ -300,14 +300,12 @@
   @override
   Widget build(BuildContext context) {
     MediaQueryData data = MediaQuery.of(context);
-    if (data != null) {
-      Size newMediaSize = data.size;
-      EdgeInsets newMediaPadding = data.padding;
-      if (newMediaSize != _mediaSize || newMediaPadding != _mediaPadding) {
-        _mediaSize = newMediaSize;
-        _mediaPadding = newMediaPadding;
-        scheduleMicrotask(_ensureVisibleIfFocused);
-      }
+    Size newMediaSize = data.size;
+    EdgeInsets newMediaPadding = data.padding;
+    if (newMediaSize != _mediaSize || newMediaPadding != _mediaPadding) {
+      _mediaSize = newMediaSize;
+      _mediaPadding = newMediaPadding;
+      scheduleMicrotask(_ensureVisibleIfFocused);
     }
     return new Semantics(
       container: true,
diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart
index 5afd18e..e4acefd 100644
--- a/packages/flutter/lib/src/widgets/media_query.dart
+++ b/packages/flutter/lib/src/widgets/media_query.dart
@@ -2,6 +2,8 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'dart:ui' as ui;
+
 import 'basic.dart';
 import 'framework.dart';
 
@@ -18,6 +20,11 @@
 class MediaQueryData {
   const MediaQueryData({ this.size, this.devicePixelRatio, this.padding });
 
+  MediaQueryData.fromWindow(ui.Window window)
+    : size = window.size,
+      devicePixelRatio = window.devicePixelRatio,
+      padding = new EdgeInsets.fromWindowPadding(window.padding);
+
   /// The size of the media (e.g, the size of the screen).
   final Size size;
 
@@ -76,7 +83,7 @@
   /// keeping your widget up-to-date.
   static MediaQueryData of(BuildContext context) {
     MediaQuery query = context.inheritFromWidgetOfExactType(MediaQuery);
-    return query == null ? null : query.data;
+    return query?.data ?? new MediaQueryData.fromWindow(ui.window);
   }
 
   @override
diff --git a/packages/flutter/test/widget/draggable_test.dart b/packages/flutter/test/widget/draggable_test.dart
index b297c9a..1bd599e 100644
--- a/packages/flutter/test/widget/draggable_test.dart
+++ b/packages/flutter/test/widget/draggable_test.dart
@@ -9,7 +9,7 @@
 void main() {
   test('Drag and drop - control test', () {
     testWidgets((WidgetTester tester) {
-      List<dynamic> accepted = <dynamic>[];
+      List<int> accepted = <int>[];
 
       tester.pumpWidget(new MaterialApp(
         routes: <String, WidgetBuilder>{
@@ -62,7 +62,7 @@
       gesture.up();
       tester.pump();
 
-      expect(accepted, equals([1]));
+      expect(accepted, equals(<int>[1]));
       expect(tester.findText('Source'), isNotNull);
       expect(tester.findText('Dragging'), isNull);
       expect(tester.findText('Target'), isNotNull);
@@ -553,4 +553,204 @@
 
     });
   });
+
+  test('Drag and drop - onDraggableDropped not called if dropped on accepting target', () {
+    testWidgets((WidgetTester tester) {
+      List<int> accepted = <int>[];
+      bool onDraggableCanceledCalled = false;
+
+      tester.pumpWidget(new MaterialApp(
+        routes: <String, WidgetBuilder>{
+          '/': (BuildContext context) { return new Column(
+            children: <Widget>[
+              new Draggable<int>(
+                data: 1,
+                child: new Text('Source'),
+                feedback: new Text('Dragging'),
+                onDraggableCanceled: (Velocity velocity, Offset offset) {
+                  onDraggableCanceledCalled = true;
+                }
+              ),
+              new DragTarget<int>(
+                builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
+                  return new Container(
+                    height: 100.0,
+                    child: new Text('Target')
+                  );
+                },
+                onAccept: (int data) {
+                  accepted.add(data);
+                }
+              ),
+            ]);
+          },
+        }
+      ));
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      Point firstLocation = tester.getCenter(tester.findText('Source'));
+      TestGesture gesture = tester.startGesture(firstLocation, pointer: 7);
+      tester.pump();
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNotNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      Point secondLocation = tester.getCenter(tester.findText('Target'));
+      gesture.moveTo(secondLocation);
+      tester.pump();
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNotNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      gesture.up();
+      tester.pump();
+
+      expect(accepted, equals(<int>[1]));
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+    });
+  });
+
+  test('Drag and drop - onDraggableDropped called if dropped on non-accepting target', () {
+    testWidgets((WidgetTester tester) {
+      List<int> accepted = <int>[];
+      bool onDraggableCanceledCalled = false;
+      Velocity onDraggableCanceledVelocity;
+      Offset onDraggableCanceledOffset;
+
+      tester.pumpWidget(new MaterialApp(
+        routes: <String, WidgetBuilder>{
+          '/': (BuildContext context) { return new Column(
+            children: <Widget>[
+              new Draggable<int>(
+                data: 1,
+                child: new Text('Source'),
+                feedback: new Text('Dragging'),
+                onDraggableCanceled: (Velocity velocity, Offset offset) {
+                  onDraggableCanceledCalled = true;
+                  onDraggableCanceledVelocity = velocity;
+                  onDraggableCanceledOffset = offset;
+                }
+              ),
+              new DragTarget<int>(
+                builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
+                  return new Container(
+                    height: 100.0,
+                    child: new Text('Target')
+                  );
+                },
+                onWillAccept: (int data) => false
+              ),
+            ]);
+          },
+        }
+      ));
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      Point firstLocation = tester.getTopLeft(tester.findText('Source'));
+      TestGesture gesture = tester.startGesture(firstLocation, pointer: 7);
+      tester.pump();
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNotNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      Point secondLocation = tester.getCenter(tester.findText('Target'));
+      gesture.moveTo(secondLocation);
+      tester.pump();
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNotNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      gesture.up();
+      tester.pump();
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isTrue);
+      expect(onDraggableCanceledVelocity, equals(Velocity.zero));
+      expect(onDraggableCanceledOffset, equals(new Offset(secondLocation.x, secondLocation.y)));
+    });
+  });
+
+  test('Drag and drop - onDraggableDropped called if dropped on non-accepting target with correct velocity', () {
+    testWidgets((WidgetTester tester) {
+      List<int> accepted = <int>[];
+      bool onDraggableCanceledCalled = false;
+      Velocity onDraggableCanceledVelocity;
+      Offset onDraggableCanceledOffset;
+
+      tester.pumpWidget(new MaterialApp(
+        routes: <String, WidgetBuilder>{
+          '/': (BuildContext context) { return new Column(
+            children: <Widget>[
+              new Draggable<int>(
+                data: 1,
+                child: new Text('Source'),
+                feedback: new Text('Source'),
+                onDraggableCanceled: (Velocity velocity, Offset offset) {
+                  onDraggableCanceledCalled = true;
+                  onDraggableCanceledVelocity = velocity;
+                  onDraggableCanceledOffset = offset;
+                }
+              ),
+              new DragTarget<int>(
+                builder: (BuildContext context, List<int> data, List<dynamic> rejects) {
+                  return new Container(
+                    height: 100.0,
+                    child: new Text('Target')
+                  );
+                },
+                onWillAccept: (int data) => false
+              ),
+            ]);
+          },
+        }
+      ));
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isFalse);
+
+      Point flingStart = tester.getTopLeft(tester.findText('Source'));
+      tester.flingFrom(flingStart, new Offset(0.0,100.0), 1000.0);
+      tester.pump();
+
+      expect(accepted, isEmpty);
+      expect(tester.findText('Source'), isNotNull);
+      expect(tester.findText('Dragging'), isNull);
+      expect(tester.findText('Target'), isNotNull);
+      expect(onDraggableCanceledCalled, isTrue);
+      expect(onDraggableCanceledVelocity.pixelsPerSecond.dx.abs(), lessThan(0.0000001));
+      expect((onDraggableCanceledVelocity.pixelsPerSecond.dy - 1000.0).abs(), lessThan(0.0000001));
+      expect(onDraggableCanceledOffset, equals(new Offset(flingStart.x, flingStart.y) + new Offset(0.0, 100.0)));
+    });
+  });
 }
diff --git a/packages/flutter/test/widget/media_query_test.dart b/packages/flutter/test/widget/media_query_test.dart
new file mode 100644
index 0000000..81ad5fe
--- /dev/null
+++ b/packages/flutter/test/widget/media_query_test.dart
@@ -0,0 +1,28 @@
+// 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:ui' as ui;
+
+import 'package:flutter_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+import 'package:test/test.dart';
+
+void main() {
+  test('MediaQuery has a default', () {
+    testWidgets((WidgetTester tester) {
+      Size size;
+
+      tester.pumpWidget(
+        new Builder(
+          builder: (BuildContext context) {
+            size = MediaQuery.of(context).size;
+            return new Container();
+          }
+        )
+      );
+
+      expect(size, equals(ui.window.size));
+    });
+  });
+}
diff --git a/packages/flutter_sprites/lib/src/constraint.dart b/packages/flutter_sprites/lib/src/constraint.dart
index 343cff5..05dfd53 100644
--- a/packages/flutter_sprites/lib/src/constraint.dart
+++ b/packages/flutter_sprites/lib/src/constraint.dart
@@ -66,6 +66,29 @@
   }
 }
 
+/// A [Constraint] that copies a node's rotation, optionally with [dampening].
+class ConstraintRotationToNodeRotation extends Constraint {
+  /// Creates a new constraint that copies a node's rotation, optionally
+  /// with a [baseRotation] added and using [dampening].
+  ConstraintRotationToNodeRotation(this.targetNode, { this.baseRotation: 0.0, this.dampening });
+
+  /// The node to copy the rotation from
+  final Node targetNode;
+
+  /// The base rotation will be added to the rotation that copied from the targetNode
+  final double baseRotation;
+
+  /// The filter factor used when constraining the rotation of the node. Valid
+  /// values are in the range 0.0 to 1.0
+  final double dampening;
+
+  @override
+  void constrain(Node node, double dt) {
+    double target = targetNode.rotation + baseRotation;
+    node.rotation = _dampenRotation(node.rotation, target, dampening);
+  }
+}
+
 /// A [Constraint] that rotates a node to point towards another node. The target
 /// node is allowed to have a different parent, but they must be in the same
 /// [SpriteBox].
diff --git a/packages/flutter_test/lib/src/instrumentation.dart b/packages/flutter_test/lib/src/instrumentation.dart
index b926465..6a3c418 100644
--- a/packages/flutter_test/lib/src/instrumentation.dart
+++ b/packages/flutter_test/lib/src/instrumentation.dart
@@ -190,7 +190,7 @@
     final double timeStampDelta = 1000.0 * offset.distance / (kMoveCount * velocity);
     double timeStamp = 0.0;
     dispatchEvent(p.down(startLocation, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
-    for(int i = 0; i < kMoveCount; i++) {
+    for(int i = 0; i <= kMoveCount; i++) {
       final Point location = startLocation + Offset.lerp(Offset.zero, offset, i / kMoveCount);
       dispatchEvent(p.move(location, timeStamp: new Duration(milliseconds: timeStamp.round())), result);
       timeStamp += timeStampDelta;
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 2db94d8..a2870eb 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -6,6 +6,7 @@
 import 'dart:io';
 
 import 'package:args/command_runner.dart';
+import 'package:path/path.dart' as path;
 import 'package:stack_trace/stack_trace.dart';
 
 import 'src/base/context.dart';
@@ -31,6 +32,7 @@
 import 'src/commands/upgrade.dart';
 import 'src/device.dart';
 import 'src/doctor.dart';
+import 'src/globals.dart';
 import 'src/runner/flutter_command_runner.dart';
 
 /// Main entry point for commands.
@@ -89,9 +91,65 @@
       // We've caught an exit code.
       exit(error.exitCode);
     } else {
-      stderr.writeln(error);
-      stderr.writeln(chain.terse);
+      // We've crashed; emit a log report.
+      stderr.writeln();
+      stderr.writeln('Oops; flutter has crashed: "$error"');
+
+      File file = _createCrashReport(args, error, chain);
+
+      stderr.writeln();
+      stderr.writeln('Crash report written to ${path.relative(file.path)}.');
+      stderr.writeln('Please let us know at https://github.com/flutter/flutter/issues!');
       exit(1);
     }
   });
 }
+
+File _createCrashReport(List<String> args, dynamic error, Chain chain) {
+  File crashFile = _createCrashFileName(Directory.current, 'flutter');
+
+  StringBuffer buf = new StringBuffer();
+
+  buf.writeln('Flutter crash report; please file at https://github.com/flutter/flutter/issues.\n');
+
+  buf.writeln('## command\n');
+  buf.writeln('flutter ${args.join(' ')}\n');
+
+  buf.writeln('## exception\n');
+  buf.writeln('$error\n');
+  buf.writeln('${chain.terse}');
+
+  buf.writeln('## flutter doctor\n');
+  buf.writeln(_doctorText());
+
+  crashFile.writeAsStringSync(buf.toString());
+
+  return crashFile;
+}
+
+File _createCrashFileName(Directory dir, String baseName) {
+  int i = 1;
+
+  while (true) {
+    String name = '${baseName}_${i.toString().padLeft(2, '0')}.log';
+    File file = new File(path.join(dir.path, name));
+    if (!file.existsSync())
+      return file;
+    i++;
+  }
+}
+
+String _doctorText() {
+  try {
+    BufferLogger logger = new BufferLogger();
+    AppContext appContext = new AppContext();
+
+    appContext[Logger] = logger;
+
+    appContext.runInZone(() => doctor.diagnose());
+
+    return logger.statusText;
+  } catch (error) {
+    return '';
+  }
+}
diff --git a/packages/flutter_tools/lib/src/android/android_device.dart b/packages/flutter_tools/lib/src/android/android_device.dart
index 6cdc73e..f03a92c 100644
--- a/packages/flutter_tools/lib/src/android/android_device.dart
+++ b/packages/flutter_tools/lib/src/android/android_device.dart
@@ -223,9 +223,9 @@
     ServiceProtocolDiscovery serviceProtocolDiscovery =
         new ServiceProtocolDiscovery(logReader);
 
-    // We take this future here but do not wait for completion until *after*
-    // we start the bundle.
-    Future<int> serviceProtocolPort = serviceProtocolDiscovery.nextPort();
+    // We take this future here but do not wait for completion until *after* we
+    // start the bundle.
+    Future<int> scrapeServicePort = serviceProtocolDiscovery.nextPort();
 
     List<String> cmd = adbCommandForDevice(<String>[
       'shell', 'am', 'start',
@@ -250,13 +250,23 @@
       return false;
     }
 
-    // Wait for the service protocol port here. This will complete once
-    // the device has printed "Observatory is listening on..."
-    int devicePort = await serviceProtocolPort;
-    printTrace('service protocol port = $devicePort');
-    await _forwardObservatoryPort(devicePort, debugPort);
+    // Wait for the service protocol port here. This will complete once the
+    // device has printed "Observatory is listening on...".
+    printTrace('Waiting for observatory port to be available...');
 
-    return true;
+    try {
+      int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12));
+      printTrace('service protocol port = $devicePort');
+      await _forwardObservatoryPort(devicePort, debugPort);
+      return true;
+    } catch (error) {
+      if (error is TimeoutException)
+        printError('Timed out while waiting for a debug connection.');
+      else
+        printError('Error waiting for a debug connection: $error');
+
+      return false;
+    }
   }
 
   @override
@@ -303,7 +313,7 @@
     return runCommandAndStreamOutput(command).then((int exitCode) => exitCode == 0);
   }
 
-  // TODO(devoncarew): Return android_arm or android_x64 based on [isLocalEmulator].
+  // TODO(devoncarew): Use isLocalEmulator to return android_arm or android_x64.
   @override
   TargetPlatform get platform => TargetPlatform.android_arm;
 
@@ -533,7 +543,9 @@
     String lastTimestamp = device.lastLogcatTimestamp;
     if (lastTimestamp != null)
       args.addAll(<String>['-T', lastTimestamp]);
-    args.addAll(<String>['-s', 'flutter:V', 'ActivityManager:W', 'System.err:W', '*:F']);
+    args.addAll(<String>[
+      '-s', 'flutter:V', 'SkyMain:V', 'AndroidRuntime:W', 'ActivityManager:W', 'System.err:W', '*:F'
+    ]);
     _process = await runCommand(device.adbCommandForDevice(args));
     _stdoutSubscription =
         _process.stdout.transform(UTF8.decoder)
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 7e9f9c9..e918a63 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -125,8 +125,7 @@
     for (BuildConfiguration config in configs) {
       switch (config.targetPlatform) {
         case TargetPlatform.android_arm:
-          assert(android == null);
-          android = new AndroidApk.fromBuildConfiguration(config);
+          android ??= new AndroidApk.fromBuildConfiguration(config);
           break;
 
         case TargetPlatform.ios:
diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart
index 513f059..2c5e375 100644
--- a/packages/flutter_tools/lib/src/artifacts.dart
+++ b/packages/flutter_tools/lib/src/artifacts.dart
@@ -72,12 +72,15 @@
 
 class ArtifactStore {
   static const List<Artifact> knownArtifacts = const <Artifact>[
+    // tester
     const Artifact._(
       name: 'Flutter Tester',
       fileName: 'sky_shell',
       type: ArtifactType.shell,
       targetPlatform: TargetPlatform.linux_x64
     ),
+
+    // snapshotters
     const Artifact._(
       name: 'Sky Snapshot',
       fileName: 'sky_snapshot',
@@ -90,6 +93,8 @@
       type: ArtifactType.snapshot,
       hostPlatform: HostPlatform.mac
     ),
+
+    // mojo
     const Artifact._(
       name: 'Flutter for Mojo',
       fileName: 'flutter.mojo',
@@ -102,6 +107,8 @@
       type: ArtifactType.mojo,
       targetPlatform: TargetPlatform.linux_x64
     ),
+
+    // android-arm
     const Artifact._(
       name: 'Compiled Java code',
       fileName: 'classes.dex.jar',
@@ -126,6 +133,8 @@
       type: ArtifactType.androidLibSkyShell,
       targetPlatform: TargetPlatform.android_arm
     ),
+
+    // iOS
     const Artifact._(
       name: 'iOS Runner (Xcode Project)',
       fileName: 'FlutterXcode.zip',
@@ -202,7 +211,7 @@
     return cacheDir;
   }
 
-  static Future<String> getPath(Artifact artifact) async {
+  static String getPath(Artifact artifact) {
     File cachedFile = new File(path.join(
         getBaseCacheDir().path, 'engine', artifact.platform, artifact.fileName
     ));
diff --git a/packages/flutter_tools/lib/src/base/process.dart b/packages/flutter_tools/lib/src/base/process.dart
index 25b8bb3..aa5302a 100644
--- a/packages/flutter_tools/lib/src/base/process.dart
+++ b/packages/flutter_tools/lib/src/base/process.dart
@@ -116,22 +116,28 @@
   printTrace(cmdText);
   ProcessResult results =
       Process.runSync(cmd[0], cmd.getRange(1, cmd.length).toList(), workingDirectory: workingDirectory);
-  if (results.exitCode != 0) {
-    String errorDescription = 'Error code ${results.exitCode} '
-        'returned when attempting to run command: ${cmd.join(' ')}';
-    printTrace(errorDescription);
-    if (results.stderr.length > 0) {
-      if (noisyErrors) {
-        printError(results.stderr.trim());
-      } else {
-        printTrace('Errors logged: ${results.stderr.trim()}');
-      }
-    }
-    if (checked)
-      throw errorDescription;
+
+  printTrace('Exit code ${results.exitCode} from: ${cmd.join(' ')}');
+
+  if (results.stdout.isNotEmpty) {
+    if (results.exitCode != 0 && noisyErrors)
+      printStatus(results.stdout.trim());
+    else
+      printTrace(results.stdout.trim());
   }
-  if (results.stdout.trim().isNotEmpty)
-    printTrace(results.stdout.trim());
+
+  if (results.exitCode != 0) {
+    if (results.stderr.isNotEmpty) {
+      if (noisyErrors)
+        printError(results.stderr.trim());
+      else
+        printTrace(results.stderr.trim());
+    }
+
+    if (checked)
+      throw 'Exit code ${results.exitCode} from: ${cmd.join(' ')}';
+  }
+
   return results.stdout.trim();
 }
 
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 6c4319c9..f974270 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -187,7 +187,10 @@
 
     await downloadToolchain();
 
+    // TODO(devoncarew): This command should take an arg for the output type (arm / x64).
+
     return await buildAndroid(
+      TargetPlatform.android_arm,
       toolchain: toolchain,
       configs: buildConfigurations,
       enginePath: runner.enginePath,
@@ -212,6 +215,7 @@
 ) async {
   List<String> artifactPaths;
   if (enginePath != null) {
+    // TODO(devoncarew): Support x64.
     artifactPaths = [
       '$enginePath/third_party/icu/android/icudtl.dat',
       '${config.buildDir}/gen/sky/shell/shell/classes.dex.jar',
@@ -225,10 +229,13 @@
       ArtifactType.androidLibSkyShell,
       ArtifactType.androidKeystore,
     ];
-    Iterable<Future<String>> pathFutures = artifactTypes.map(
-        (ArtifactType type) => ArtifactStore.getPath(ArtifactStore.getArtifact(
-            type: type, targetPlatform: TargetPlatform.android_arm)));
-    artifactPaths = await Future.wait(pathFutures);
+    Iterable<String> pathFutures = artifactTypes.map((ArtifactType type) {
+      return ArtifactStore.getPath(ArtifactStore.getArtifact(
+        type: type,
+        targetPlatform: config.targetPlatform
+      ));
+    });
+    artifactPaths = pathFutures.toList();
   }
 
   _ApkComponents components = new _ApkComponents();
@@ -254,9 +261,14 @@
 }
 
 int _buildApk(
-  _ApkComponents components, String flxPath, ApkKeystoreInfo keystore, String outputFile
+  TargetPlatform platform,
+  _ApkComponents components,
+  String flxPath,
+  ApkKeystoreInfo keystore,
+  String outputFile
 ) {
   Directory tempDir = Directory.systemTemp.createTempSync('flutter_tools');
+
   try {
     _ApkBuilder builder = new _ApkBuilder(androidSdk.latestVersion);
 
@@ -273,7 +285,9 @@
 
     _AssetBuilder artifactBuilder = new _AssetBuilder(tempDir, 'artifacts');
     artifactBuilder.add(classesDex, 'classes.dex');
-    artifactBuilder.add(components.libSkyShell, 'lib/armeabi-v7a/libsky_shell.so');
+    // x86? x86_64?
+    String abiDir = platform == TargetPlatform.android_arm ? 'armeabi-v7a' : 'x86';
+    artifactBuilder.add(components.libSkyShell, 'lib/$abiDir/libsky_shell.so');
 
     File unalignedApk = new File('${tempDir.path}/app.apk.unaligned');
     builder.package(
@@ -359,7 +373,8 @@
   return false;
 }
 
-Future<int> buildAndroid({
+Future<int> buildAndroid(
+  TargetPlatform platform, {
   Toolchain toolchain,
   List<BuildConfiguration> configs,
   String enginePath,
@@ -367,7 +382,7 @@
   String manifest: _kDefaultAndroidManifestPath,
   String resources,
   String outputFile: _kDefaultOutputPath,
-  String target: '',
+  String target,
   String flxPath,
   ApkKeystoreInfo keystore
 }) async {
@@ -399,10 +414,9 @@
       resources = _kDefaultResourcesPath;
   }
 
-  BuildConfiguration config = configs.firstWhere(
-    (BuildConfiguration bc) => bc.targetPlatform == TargetPlatform.android_arm
-  );
+  BuildConfiguration config = configs.firstWhere((BuildConfiguration bc) => bc.targetPlatform == platform);
   _ApkComponents components = await _findApkComponents(config, enginePath, manifest, resources);
+
   if (components == null) {
     printError('Failure building APK. Unable to find components.');
     return 1;
@@ -416,36 +430,34 @@
       printError('(Omit the --flx option to build the FLX automatically)');
       return 1;
     }
-    return _buildApk(components, flxPath, keystore, outputFile);
+
+    return _buildApk(platform, components, flxPath, keystore, outputFile);
   } else {
     // Find the path to the main Dart file; build the FLX.
     String mainPath = findMainDartFile(target);
     String localBundlePath = await flx.buildFlx(toolchain, mainPath: mainPath);
 
-    return _buildApk(components, localBundlePath, keystore, outputFile);
+    return _buildApk(platform, components, localBundlePath, keystore, outputFile);
   }
 }
 
 // TODO(mpcomplete): move this to Device?
 /// This is currently Android specific.
-Future<int> buildAll(
-  List<Device> devices,
+Future<int> buildForDevice(
+  Device device,
   ApplicationPackageStore applicationPackages,
   Toolchain toolchain,
   List<BuildConfiguration> configs, {
   String enginePath,
   String target: ''
 }) async {
-  for (Device device in devices) {
-    ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
-    if (package == null)
-      continue;
+  ApplicationPackage package = applicationPackages.getPackageForPlatform(device.platform);
+  if (package == null)
+    return 0;
 
-    // TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
-    if (package != applicationPackages.android)
-      continue;
-
-    int result = await build(toolchain, configs, enginePath: enginePath, target: target);
+  // TODO(mpcomplete): Temporary hack. We only support the apk builder atm.
+  if (package == applicationPackages.android) {
+    int result = await build(device.platform, toolchain, configs, enginePath: enginePath, target: target);
     if (result != 0)
       return result;
   }
@@ -454,10 +466,11 @@
 }
 
 Future<int> build(
+  TargetPlatform platform,
   Toolchain toolchain,
   List<BuildConfiguration> configs, {
   String enginePath,
-  String target: ''
+  String target
 }) async {
   if (!FileSystemEntity.isFileSync(_kDefaultAndroidManifestPath)) {
     printError('Cannot build APK. Missing $_kDefaultAndroidManifestPath.');
@@ -465,6 +478,7 @@
   }
 
   int result = await buildAndroid(
+    platform,
     toolchain: toolchain,
     configs: configs,
     enginePath: enginePath,
diff --git a/packages/flutter_tools/lib/src/commands/drive.dart b/packages/flutter_tools/lib/src/commands/drive.dart
index 18581e2..83171af 100644
--- a/packages/flutter_tools/lib/src/commands/drive.dart
+++ b/packages/flutter_tools/lib/src/commands/drive.dart
@@ -245,8 +245,10 @@
   // TODO(devoncarew): We should remove the need to special case here.
   if (command.device is AndroidDevice) {
     printTrace('Building an APK.');
-    int result = await build_apk.build(command.toolchain, command.buildConfigurations,
-      enginePath: command.runner.enginePath, target: command.target);
+    int result = await build_apk.build(
+      command.device.platform, command.toolchain, command.buildConfigurations,
+      enginePath: command.runner.enginePath, target: command.target
+    );
 
     if (result != 0)
       return result;
diff --git a/packages/flutter_tools/lib/src/commands/run.dart b/packages/flutter_tools/lib/src/commands/run.dart
index 9aafbee..aff777a 100644
--- a/packages/flutter_tools/lib/src/commands/run.dart
+++ b/packages/flutter_tools/lib/src/commands/run.dart
@@ -182,8 +182,8 @@
 
   if (install) {
     printTrace('Running build command.');
-    int result = await buildAll(
-      <Device>[device], applicationPackages, toolchain, configs,
+    int result = await buildForDevice(
+      device, applicationPackages, toolchain, configs,
       enginePath: enginePath,
       target: target
     );
@@ -232,15 +232,8 @@
     platformArgs: platformArgs
   );
 
-  if (!result) {
+  if (!result)
     printError('Error running application on ${device.name}.');
-  } else {
-    // If the user specified --start-paused (and the device supports it) then
-    // wait for the observatory port to become available before returning from
-    // `startApp()`.
-    if (startPaused && device.supportsStartPaused)
-      await delayUntilObservatoryAvailable('localhost', debugPort);
-  }
 
   return result ? 0 : 2;
 }
diff --git a/packages/flutter_tools/lib/src/commands/run_mojo.dart b/packages/flutter_tools/lib/src/commands/run_mojo.dart
index cf2d535..c1e4034 100644
--- a/packages/flutter_tools/lib/src/commands/run_mojo.dart
+++ b/packages/flutter_tools/lib/src/commands/run_mojo.dart
@@ -99,7 +99,7 @@
     if (config == null || config.type == BuildType.prebuilt) {
       TargetPlatform targetPlatform = argResults['android'] ? TargetPlatform.android_arm : TargetPlatform.linux_x64;
       Artifact artifact = ArtifactStore.getArtifact(type: ArtifactType.mojo, targetPlatform: targetPlatform);
-      flutterPath = _makePathAbsolute(await ArtifactStore.getPath(artifact));
+      flutterPath = _makePathAbsolute(ArtifactStore.getPath(artifact));
     } else {
       String localPath = path.join(config.buildDir, 'flutter.mojo');
       flutterPath = _makePathAbsolute(localPath);
diff --git a/packages/flutter_tools/lib/src/commands/test.dart b/packages/flutter_tools/lib/src/commands/test.dart
index 50d1e8e..4828cbb 100644
--- a/packages/flutter_tools/lib/src/commands/test.dart
+++ b/packages/flutter_tools/lib/src/commands/test.dart
@@ -49,7 +49,7 @@
     if (config.type == BuildType.prebuilt) {
       Artifact artifact = ArtifactStore.getArtifact(
         type: ArtifactType.shell, targetPlatform: config.targetPlatform);
-      return await ArtifactStore.getPath(artifact);
+      return ArtifactStore.getPath(artifact);
     } else {
       switch (config.targetPlatform) {
         case TargetPlatform.linux_x64:
diff --git a/packages/flutter_tools/lib/src/flx.dart b/packages/flutter_tools/lib/src/flx.dart
index d9e89da..635423c 100644
--- a/packages/flutter_tools/lib/src/flx.dart
+++ b/packages/flutter_tools/lib/src/flx.dart
@@ -8,6 +8,7 @@
 
 import 'package:flx/bundle.dart';
 import 'package:flx/signing.dart';
+import 'package:json_schema/json_schema.dart';
 import 'package:path/path.dart' as path;
 import 'package:yaml/yaml.dart';
 
@@ -108,6 +109,20 @@
   return loadYaml(manifestDescriptor);
 }
 
+Future<int> _validateManifest(Object manifest) async {
+  String schemaPath = path.join(path.absolute(ArtifactStore.flutterRoot),
+      'packages', 'flutter_tools', 'schema', 'flutter_yaml.json');
+  Schema schema = await Schema.createSchemaFromUrl('file://$schemaPath');
+
+  Validator validator = new Validator(schema);
+  if (validator.validate(manifest))
+    return 0;
+
+  printError('Error in flutter.yaml:');
+  printError(validator.errors.join('\n'));
+  return 1;
+}
+
 ZipEntry _createAssetEntry(_Asset asset) {
   String source = asset.source ?? asset.key;
   File file = new File('${asset.base}/$source');
@@ -184,7 +199,14 @@
   String workingDirPath: defaultWorkingDirPath,
   bool precompiledSnapshot: false
 }) async {
-  Map<String, dynamic> manifestDescriptor = _loadManifest(manifestPath);
+  Object manifest = _loadManifest(manifestPath);
+  if (manifest != null) {
+    int result = await _validateManifest(manifest);
+    if (result != 0)
+      return result;
+  }
+  Map<String, dynamic> manifestDescriptor = manifest;
+
   String assetBasePath = path.dirname(path.absolute(manifestPath));
 
   File snapshotFile;
diff --git a/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart b/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
index 5de3a94..0777f82 100644
--- a/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
@@ -104,8 +104,8 @@
     targetPlatform: TargetPlatform.ios
   );
 
-  String xcodeProjectPath = await ArtifactStore.getPath(xcodeProject);
-  List<int> archiveBytes = await new File(xcodeProjectPath).readAsBytes();
+  String xcodeProjectPath = ArtifactStore.getPath(xcodeProject);
+  List<int> archiveBytes = new File(xcodeProjectPath).readAsBytesSync();
 
   if (archiveBytes.isEmpty) {
     printError('Error: No archive bytes received.');
diff --git a/packages/flutter_tools/lib/src/ios/simulators.dart b/packages/flutter_tools/lib/src/ios/simulators.dart
index 1b89bc4..151f808 100644
--- a/packages/flutter_tools/lib/src/ios/simulators.dart
+++ b/packages/flutter_tools/lib/src/ios/simulators.dart
@@ -459,9 +459,9 @@
     ServiceProtocolDiscovery serviceProtocolDiscovery =
         new ServiceProtocolDiscovery(logReader);
 
-    // We take this future here but do not wait for completion until *after*
-    // we start the application.
-    Future<int> serviceProtocolPort = serviceProtocolDiscovery.nextPort();
+    // We take this future here but do not wait for completion until *after* we
+    // start the application.
+    Future<int> scrapeServicePort = serviceProtocolDiscovery.nextPort();
 
     // Prepare launch arguments.
     List<String> args = <String>[
@@ -487,14 +487,23 @@
       return false;
     }
 
-    // Wait for the service protocol port here. This will complete once
-    // the device has printed "Observatory is listening on..."
-    int devicePort = await serviceProtocolPort;
-    printTrace('service protocol port = $devicePort');
-    printTrace('Successfully started ${app.name} on $id.');
-    printStatus('Observatory listening on http://127.0.0.1:$devicePort');
+    // Wait for the service protocol port here. This will complete once the
+    // device has printed "Observatory is listening on..."
+    printTrace('Waiting for observatory port to be available...');
 
-    return true;
+    try {
+      int devicePort = await scrapeServicePort.timeout(new Duration(seconds: 12));
+      printTrace('service protocol port = $devicePort');
+      printStatus('Observatory listening on http://127.0.0.1:$devicePort');
+      return true;
+    } catch (error) {
+      if (error is TimeoutException)
+        printError('Timed out while waiting for a debug connection.');
+      else
+        printError('Error waiting for a debug connection: $error');
+
+      return false;
+    }
   }
 
   bool _applicationIsInstalledAndRunning(ApplicationPackage app) {
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
index 6a3b797..12677af0 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart
@@ -167,8 +167,9 @@
   @override
   Future<dynamic> run(Iterable<String> args) {
     return super.run(args).then((dynamic result) {
-      logger.flush();
       return result;
+    }).whenComplete(() {
+      logger.flush();
     });
   }
 
diff --git a/packages/flutter_tools/lib/src/toolchain.dart b/packages/flutter_tools/lib/src/toolchain.dart
index 7684a49..9f4a9a5 100644
--- a/packages/flutter_tools/lib/src/toolchain.dart
+++ b/packages/flutter_tools/lib/src/toolchain.dart
@@ -36,7 +36,7 @@
   }
 }
 
-Future<String> _getCompilerPath(BuildConfiguration config) async {
+String _getCompilerPath(BuildConfiguration config) {
   if (config.type != BuildType.prebuilt) {
     String compilerPath = path.join(config.buildDir, 'clang_x64', 'sky_snapshot');
     if (FileSystemEntity.isFileSync(compilerPath))
@@ -48,7 +48,7 @@
   }
   Artifact artifact = ArtifactStore.getArtifact(
     type: ArtifactType.snapshot, hostPlatform: config.hostPlatform);
-  return await ArtifactStore.getPath(artifact);
+  return ArtifactStore.getPath(artifact);
 }
 
 class Toolchain {
@@ -58,7 +58,7 @@
 
   static Future<Toolchain> forConfigs(List<BuildConfiguration> configs) async {
     for (BuildConfiguration config in configs) {
-      String compilerPath = await _getCompilerPath(config);
+      String compilerPath = _getCompilerPath(config);
       if (compilerPath != null)
         return new Toolchain(compiler: new SnapshotCompiler(compilerPath));
     }
diff --git a/packages/flutter_tools/pubspec.yaml b/packages/flutter_tools/pubspec.yaml
index da787ce..ec753bd 100644
--- a/packages/flutter_tools/pubspec.yaml
+++ b/packages/flutter_tools/pubspec.yaml
@@ -14,6 +14,7 @@
   crypto: ^0.9.2
   den_api: ^0.1.0
   file: ^0.1.0
+  json_schema: ^1.0.3
   mustache4dart: ^1.0.0
   path: ^1.3.0
   pub_semver: ^1.0.0
diff --git a/packages/flutter_tools/schema/flutter_yaml.json b/packages/flutter_tools/schema/flutter_yaml.json
new file mode 100644
index 0000000..2342990
--- /dev/null
+++ b/packages/flutter_tools/schema/flutter_yaml.json
@@ -0,0 +1,36 @@
+{
+    "$schema": "http://json-schema.org/draft-04/schema#",
+    "title": "flutter.yaml",
+    "type": "object",
+    "additionalProperties": false,
+    "properties": {
+        "name": { "type": "string" },
+        "uses-material-design": { "type": "boolean" },
+        "assets": {
+            "type": "array",
+            "items": { "type": "string" }
+        },
+        "fonts": {
+            "type": "array",
+            "items": {
+                "type": "object",
+                "additionalProperties": false,
+                "properties": {
+                    "family": { "type": "string" },
+                    "fonts": {
+                        "type": "array",
+                        "items": {
+                            "type": "object",
+                            "additionalProperties": false,
+                            "properties": {
+                                "asset": { "type": "string" },
+                                "weight": { "type": "integer" },
+                                "style": { "enum": [ "normal", "italic" ] }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+}