Initial implementation of AnnotatedRegion for system chrome (#17672)

diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart
index cf69e2d..dc91c61 100644
--- a/packages/flutter/lib/src/cupertino/nav_bar.dart
+++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -292,28 +292,23 @@
   Color backgroundColor,
   Widget child,
 }) {
+
+  final bool darkBackground = backgroundColor.computeLuminance() < 0.179;
+  final SystemUiOverlayStyle overlayStyle = darkBackground
+      ? SystemUiOverlayStyle.light
+      : SystemUiOverlayStyle.dark;
   final DecoratedBox childWithBackground = new DecoratedBox(
     decoration: new BoxDecoration(
       border: border,
       color: backgroundColor,
     ),
-    child: child,
+    child: new AnnotatedRegion<SystemUiOverlayStyle>(
+      value: overlayStyle,
+      sized: true,
+      child: child,
+    ),
   );
 
-  final bool darkBackground = backgroundColor.computeLuminance() < 0.179;
-  // TODO(jonahwilliams): remove once we have platform themes.
-  switch (defaultTargetPlatform) {
-    case TargetPlatform.iOS:
-      SystemChrome.setSystemUIOverlayStyle(
-        darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark
-      );
-      break;
-    case TargetPlatform.android:
-    case TargetPlatform.fuchsia:
-      SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
-  }
-
-
   if (backgroundColor.alpha == 0xFF)
     return childWithBackground;
 
diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart
index 7ba0242..6a7079c 100644
--- a/packages/flutter/lib/src/material/app.dart
+++ b/packages/flutter/lib/src/material/app.dart
@@ -702,7 +702,7 @@
         },
       )
     );
-
+    
     assert(() {
       if (widget.debugShowMaterialGrid) {
         result = new GridPaper(
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index ca472f3..e02cf13 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -345,21 +345,6 @@
     TextStyle centerStyle = widget.textTheme?.title ?? themeData.primaryTextTheme.title;
     TextStyle sideStyle = widget.textTheme?.body1 ?? themeData.primaryTextTheme.body1;
 
-    if (parentRoute?.isCurrent ?? true) {
-      final Brightness brightness = widget.brightness ?? themeData.primaryColorBrightness;
-      // TODO(jonahwilliams): remove once we have platform themes.
-      switch (defaultTargetPlatform) {
-        case TargetPlatform.iOS:
-          SystemChrome.setSystemUIOverlayStyle(brightness == Brightness.dark
-              ? SystemUiOverlayStyle.light
-              : SystemUiOverlayStyle.dark);
-          break;
-        case TargetPlatform.android:
-        case TargetPlatform.fuchsia:
-          SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
-      }
-    }
-
     if (widget.toolbarOpacity != 1.0) {
       final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity);
       if (centerStyle?.color != null)
@@ -451,7 +436,6 @@
         ),
       ),
     );
-
     if (widget.bottom != null) {
       appBar = new Column(
         mainAxisAlignment: MainAxisAlignment.spaceBetween,
@@ -492,14 +476,21 @@
         ],
       );
     }
+    final Brightness brightness = widget.brightness ?? themeData.primaryColorBrightness;
+    final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark
+        ? SystemUiOverlayStyle.dark
+        : SystemUiOverlayStyle.light;
 
     return new Semantics(
       container: true,
       explicitChildNodes: true,
-      child: new Material(
-        color: widget.backgroundColor ?? themeData.primaryColor,
-        elevation: widget.elevation,
-        child: appBar,
+      child: new AnnotatedRegion<SystemUiOverlayStyle>(
+        value: overlayStyle,
+        child: new Material(
+          color: widget.backgroundColor ?? themeData.primaryColor,
+          elevation: widget.elevation,
+          child: appBar,
+        ),
       ),
     );
   }
diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart
index 8dce68b..3ee6643 100644
--- a/packages/flutter/lib/src/rendering/layer.dart
+++ b/packages/flutter/lib/src/rendering/layer.dart
@@ -89,6 +89,19 @@
     assert(!attached);
   }
 
+  /// Returns the value of [S] that corresponds to the point described by
+  /// [regionOffset].
+  ///
+  /// Returns null if no matching region is found.
+  ///
+  /// The main way for a value to be assigned here is by pushing an
+  /// [AnnotatedRegionLayer] into the layer tree.
+  ///
+  /// See also:
+  ///
+  ///   * [AnnotatedRegionLayer], for placing values in the layer tree.
+  S find<S>(Offset regionOffset);
+
   /// Override this method to upload this layer to the engine.
   ///
   /// The `layerOffset` is the accumulated offset of this layer's parent from the
@@ -166,6 +179,9 @@
     super.debugFillProperties(properties);
     properties.add(new DiagnosticsProperty<Rect>('paint bounds', canvasBounds));
   }
+
+  @override
+  S find<S>(Offset regionOffset) => null;
 }
 
 /// A composited layer that maps a backend texture to a rectangle.
@@ -218,6 +234,9 @@
       height: shiftedRect.height,
     );
   }
+
+  @override
+  S find<S>(Offset regionOffset) => null;
 }
 
 /// A layer that indicates to the compositor that it should display
@@ -280,6 +299,9 @@
     builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
     builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
   }
+
+  @override
+  S find<S>(Offset regionOffset) => null;
 }
 
 /// A composited layer that has a list of children.
@@ -317,6 +339,19 @@
   }
 
   @override
+  S find<S>(Offset regionOffset) {
+    Layer current = lastChild;
+    while (current != null) {
+      final Object value = current.find<S>(regionOffset);
+      if (value != null) {
+        return value;
+      }
+      current = current.previousSibling;
+    }
+    return null;
+  }
+
+  @override
   void attach(Object owner) {
     super.attach(owner);
     Layer child = firstChild;
@@ -507,6 +542,11 @@
   Offset offset;
 
   @override
+  S find<S>(Offset regionOffset) {
+    return super.find<S>(regionOffset - offset);
+  }
+
+  @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
     addChildrenToScene(builder, offset + layerOffset);
   }
@@ -574,6 +614,13 @@
   Rect clipRect;
 
   @override
+  S find<S>(Offset regionOffset) {
+    if (!clipRect.contains(regionOffset))
+      return null;
+    return super.find<S>(regionOffset);
+  }
+
+  @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
     bool enabled = true;
     assert(() {
@@ -613,6 +660,13 @@
   RRect clipRRect;
 
   @override
+  S find<S>(Offset regionOffset) {
+    if (!clipRRect.contains(regionOffset))
+      return null;
+    return super.find<S>(regionOffset);
+  }
+
+  @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
     bool enabled = true;
     assert(() {
@@ -652,6 +706,13 @@
   Path clipPath;
 
   @override
+  S find<S>(Offset regionOffset) {
+    if (!clipPath.contains(regionOffset))
+      return null;
+    return super.find<S>(regionOffset);
+  }
+
+  @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
     bool enabled = true;
     assert(() {
@@ -676,7 +737,9 @@
   ///
   /// The [transform] and [offset] properties must be non-null before the
   /// compositing phase of the pipeline.
-  TransformLayer({ this.transform, Offset offset = Offset.zero }) : super(offset: offset);
+  TransformLayer({ Matrix4 transform, Offset offset = Offset.zero })
+    : _transform = transform,
+      super(offset: offset);
 
   /// The matrix to apply.
   ///
@@ -687,9 +750,17 @@
   ///
   /// The [transform] property must be non-null before the compositing phase of
   /// the pipeline.
-  Matrix4 transform;
+  Matrix4 get transform => _transform;
+  Matrix4 _transform;
+  set transform(Matrix4 value) {
+    if (value == _transform)
+      return;
+    _transform = value;
+    _invertedTransform = null;
+  }
 
   Matrix4 _lastEffectiveTransform;
+  Matrix4 _invertedTransform;
 
   @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
@@ -705,6 +776,14 @@
   }
 
   @override
+  S find<S>(Offset regionOffset) {
+    _invertedTransform ??= new Matrix4.inverted(transform);
+    final Vector4 vector = new Vector4(regionOffset.dx, regionOffset.dy, 0.0, 1.0);
+    final Vector4 result = _invertedTransform.transform(vector);
+    return super.find<S>(new Offset(result[0], result[1]));
+  }
+
+  @override
   void applyTransform(Layer child, Matrix4 transform) {
     assert(child != null);
     assert(transform != null);
@@ -876,6 +955,13 @@
   Color shadowColor;
 
   @override
+  S find<S>(Offset regionOffset) {
+    if (!clipPath.contains(regionOffset))
+      return null;
+    return super.find<S>(regionOffset);
+  }
+
+  @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
     bool enabled = true;
     assert(() {
@@ -979,6 +1065,11 @@
   Offset _lastOffset;
 
   @override
+  S find<S>(Offset regionOffset) {
+    return super.find(regionOffset - offset);
+  }
+
+  @override
   void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
     assert(offset != null);
     _lastOffset = offset + layerOffset;
@@ -1086,6 +1177,23 @@
 
   Offset _lastOffset;
   Matrix4 _lastTransform;
+  Matrix4 _invertedTransform;
+
+  @override
+  S find<S>(Offset regionOffset) {
+    if (link.leader == null) {
+      return showWhenUnlinked ? super.find<S>(regionOffset - unlinkedOffset) : null;
+    }
+    if (_invertedTransform == null) {
+      final Matrix4 transform = getLastTransform();
+      assert(transform != null);
+      _invertedTransform = new Matrix4.zero();
+      transform.copyInverse(_invertedTransform);
+    }
+    final Vector4 vector = new Vector4(regionOffset.dx, regionOffset.dy, 0.0, 1.0);
+    final Vector4 result = _invertedTransform.transform(vector);
+    return super.find<S>(new Offset(result[0] - linkedOffset.dx, result[1] - linkedOffset.dy));
+  }
 
   /// The transform that was used during the last composition phase.
   ///
@@ -1199,3 +1307,46 @@
     properties.add(new TransformProperty('transform', getLastTransform(), defaultValue: null));
   }
 }
+
+/// A composited layer which annotates its children with a value.
+///
+/// These values can be retrieved using [Layer.find] with a given [Offset]. If
+/// a [Size] is provided to this layer, then find will check if the provided
+/// offset is within the bounds of the layer.
+class AnnotatedRegionLayer<T> extends ContainerLayer {
+  /// Creates a new layer annotated with [value] that clips to [size] if provided.
+  ///
+  /// The value provided cannot be null.
+  AnnotatedRegionLayer(this.value, {this.size}) : assert(value != null);
+
+  /// The value returned by [find] if the offset is contained within this layer.
+  final T value;
+
+  /// The [size] is optionally used to clip the hit-testing of [find].
+  ///
+  /// If not provided, all offsets are considered to be contained within this
+  /// layer, unless an ancestor layer applies a clip.
+  final Size size;
+
+  @override
+  S find<S>(Offset regionOffset) {
+    final S result = super.find<S>(regionOffset);
+    if (result != null)
+      return result;
+    if (size != null && !size.contains(regionOffset))
+      return null;
+    if (T == S) {
+      final Object untypedResult = value;
+      final S typedResult = untypedResult;
+      return typedResult;
+    }
+    return super.find<S>(regionOffset);
+  }
+
+  @override
+  void debugFillProperties(DiagnosticPropertiesBuilder properties) {
+    super.debugFillProperties(properties);
+    properties.add(new DiagnosticsProperty<T>('value', value));
+    properties.add(new DiagnosticsProperty<Size>('size', size, defaultValue: null));
+  }
+}
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index 2705c06..dd12748 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -4235,3 +4235,58 @@
     properties.add(new TransformProperty('current transform matrix', getCurrentTransform()));
   }
 }
+
+/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree.
+///
+/// See also:
+///
+///   * [Layer.find], for an example of how this value is retrieved.
+///   * [AnnotatedRegionLayer], the layer this render object creates.
+class RenderAnnotatedRegion<T> extends RenderProxyBox {
+
+  /// Creates a new [RenderAnnotatedRegion] to insert [value] into the
+  /// layer tree.
+  ///
+  /// If [sized] is true, the layer is provided with the size of this render
+  /// object to clip the results of [Layer.findRegion].
+  ///
+  /// Neither [value] nor [sized] can be null.
+  RenderAnnotatedRegion({
+    @required T value,
+    @required bool sized,
+    RenderBox child,
+  }) : assert(value != null),
+       assert(sized != null),
+       _value = value,
+       _sized = sized,
+       super(child);
+
+  /// A value which can be retrieved using [Layer.find].
+  T get value => _value;
+  T _value;
+  set value (T newValue) {
+    if (_value == newValue)
+      return;
+    _value = newValue;
+    markNeedsPaint();
+  }
+
+  /// Whether the render object will pass its [size] to the [AnnotatedRegionLayer].
+  bool get sized => _sized;
+  bool _sized;
+  set sized(bool value) {
+    if (_sized == value)
+      return;
+    _sized = value;
+    markNeedsPaint();
+  }
+
+  @override
+  final bool alwaysNeedsCompositing = true;
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    final AnnotatedRegionLayer<T> layer = new AnnotatedRegionLayer<T>(value, size: sized ? size : null);
+    context.pushLayer(layer, super.paint, offset);
+  }
+}
diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart
index 268773b..bdc0a17 100644
--- a/packages/flutter/lib/src/rendering/view.dart
+++ b/packages/flutter/lib/src/rendering/view.dart
@@ -7,6 +7,7 @@
 import 'dart:ui' as ui show Scene, SceneBuilder, window;
 
 import 'package:flutter/foundation.dart';
+import 'package:flutter/services.dart';
 import 'package:vector_math/vector_math_64.dart';
 
 import 'binding.dart';
@@ -81,6 +82,27 @@
     markNeedsLayout();
   }
 
+  /// Whether Flutter should automatically compute the desired system UI.
+  ///
+  /// When this setting is enabled, Flutter will hit-test the layer tree at the
+  /// top and bottom of the screen on each frame looking for an
+  /// [AnnotatedRegionLayer] with an instance of a [SystemUiOverlayStyle]. The
+  /// hit-test result from the top of the screen provides the status bar settings
+  /// and the hit-test result from the bottom of the screen provides the system
+  /// nav bar settings.
+  ///
+  /// Setting this to false does not cause previous automatic adjustments to be
+  /// reset, nor does setting it to true cause the app to update immediately.
+  ///
+  /// If you want to imperatively set the system ui style instead, it is
+  /// recommended that [automaticSystemUiAdjustment] is set to false.
+  ///
+  /// See also:
+  ///
+  ///   * [AnnotatedRegion], for placing [SystemUiOverlayStyle] in the layer tree.
+  ///   * [SystemChrome.setSystemUIOverlayStyle], for imperatively setting the system ui style.
+  bool automaticSystemUiAdjustment = true;
+
   /// Bootstrap the rendering pipeline by scheduling the first frame.
   ///
   /// This should only be called once, and must be called before changing
@@ -172,6 +194,8 @@
       final ui.SceneBuilder builder = new ui.SceneBuilder();
       layer.addToScene(builder, Offset.zero);
       final ui.Scene scene = builder.build();
+      if (automaticSystemUiAdjustment)
+        _updateSystemChrome();
       ui.window.render(scene);
       scene.dispose();
       assert(() {
@@ -184,6 +208,35 @@
     }
   }
 
+  void _updateSystemChrome() {
+    final Rect bounds = paintBounds;
+    final Offset top = new Offset(bounds.center.dx, ui.window.padding.top / ui.window.devicePixelRatio);
+    final Offset bottom = new Offset(bounds.center.dx, bounds.center.dy - ui.window.padding.bottom / ui.window.devicePixelRatio);
+    final SystemUiOverlayStyle upperOverlayStyle = layer.find<SystemUiOverlayStyle>(top);
+    // Only android has a customizable system navigation bar.
+    SystemUiOverlayStyle lowerOverlayStyle; 
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+        lowerOverlayStyle = layer.find<SystemUiOverlayStyle>(bottom);
+        break;
+      case TargetPlatform.iOS:
+      case TargetPlatform.fuchsia:
+        break;
+    }
+    // If there are no overlay styles in the UI don't bother updating.
+    if (upperOverlayStyle != null || lowerOverlayStyle != null) {
+      final SystemUiOverlayStyle overlayStyle = new SystemUiOverlayStyle(
+        statusBarBrightness: upperOverlayStyle?.statusBarBrightness,
+        statusBarIconBrightness: upperOverlayStyle?.statusBarIconBrightness,
+        statusBarColor: upperOverlayStyle?.statusBarColor,
+        systemNavigationBarColor: lowerOverlayStyle?.systemNavigationBarColor,
+        systemNavigationBarDividerColor: lowerOverlayStyle?.systemNavigationBarDividerColor,
+        systemNavigationBarIconBrightness: lowerOverlayStyle?.systemNavigationBarIconBrightness,
+      );
+      SystemChrome.setSystemUIOverlayStyle(overlayStyle);
+    }
+  }
+
   @override
   Rect get paintBounds => Offset.zero & (size * configuration.devicePixelRatio);
 
diff --git a/packages/flutter/lib/src/services/system_chrome.dart b/packages/flutter/lib/src/services/system_chrome.dart
index 622b890..0c9f864 100644
--- a/packages/flutter/lib/src/services/system_chrome.dart
+++ b/packages/flutter/lib/src/services/system_chrome.dart
@@ -171,6 +171,9 @@
     };
   }
 
+  @override
+  String toString() => _toMap().toString();
+
   /// Creates a copy of this theme with the given fields replaced with new values.
   SystemUiOverlayStyle copyWith({
     Color systemNavigationBarColor,
@@ -302,19 +305,16 @@
   /// ```
   static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
     assert(style != null);
-
     if (_pendingStyle != null) {
       // The microtask has already been queued; just update the pending value.
       _pendingStyle = style;
       return;
     }
-
     if (style == _latestStyle) {
       // Trivial success: no microtask has been queued and the given style is
       // already in effect, so no need to queue a microtask.
       return;
     }
-
     _pendingStyle = style;
     scheduleMicrotask(() {
       assert(_pendingStyle != null);
diff --git a/packages/flutter/lib/src/widgets/annotated_region.dart b/packages/flutter/lib/src/widgets/annotated_region.dart
new file mode 100644
index 0000000..f14d7fd
--- /dev/null
+++ b/packages/flutter/lib/src/widgets/annotated_region.dart
@@ -0,0 +1,57 @@
+// Copyright 2018 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/foundation.dart';
+import 'package:flutter/rendering.dart';
+
+import 'framework.dart';
+
+/// Annotates a region of the layer tree with a value.
+///
+/// See also:
+///
+///   * [Layer.find], for an example of how this value is retrieved.
+///   * [AnnotatedRegionLayer], the layer pushed into the layer tree.
+class AnnotatedRegion<T> extends SingleChildRenderObjectWidget {
+  /// Creates a new annotated region to insert [value] into the layer tree.
+  ///
+  /// Neither [child] nor [value] may be null.
+  ///
+  /// [sized] defaults to true and controls whether the annotated region will
+  /// clip its child.
+  const AnnotatedRegion({
+    Key key,
+    @required Widget child,
+    @required this.value,
+    this.sized = true,
+  }) : assert(value != null),
+       assert(child != null),
+       super(key: key, child: child);
+
+  /// A value which can be retrieved using [Layer.find].
+  final T value;
+
+  /// If false, the layer pushed into the tree will not be provided with a size.
+  ///
+  /// An [AnnotatedRegionLayer] with a size checks that the offset provided in
+  /// [Layer.find] is within the bounds, returning null otherwise.
+  ///
+  /// See also:
+  ///
+  ///   * [AnnotatedRegionLayer], for a description of this behavior.
+  final bool sized;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return new RenderAnnotatedRegion<T>(value: value, sized: sized);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, RenderAnnotatedRegion<T> renderObject) {
+    renderObject
+      ..value = value
+      ..sized = sized;
+  }
+}
+
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart
index 248f610..f100ca3 100644
--- a/packages/flutter/lib/src/widgets/widget_inspector.dart
+++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -1703,6 +1703,9 @@
     _textPainter.paint(canvas, tipOffset + const Offset(_kTooltipPadding, _kTooltipPadding));
     canvas.restore();
   }
+
+  @override
+  S find<S>(Offset regionOffset) => null;
 }
 
 const double _kScreenEdgeMargin = 10.0;
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart
index 95aad44..9b64a7b 100644
--- a/packages/flutter/lib/widgets.dart
+++ b/packages/flutter/lib/widgets.dart
@@ -18,6 +18,7 @@
 export 'src/widgets/animated_list.dart';
 export 'src/widgets/animated_size.dart';
 export 'src/widgets/animated_switcher.dart';
+export 'src/widgets/annotated_region.dart';
 export 'src/widgets/app.dart';
 export 'src/widgets/async.dart';
 export 'src/widgets/automatic_keep_alive.dart';
diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart
index 6801c68..04a3896 100644
--- a/packages/flutter/test/material/ink_well_test.dart
+++ b/packages/flutter/test/material/ink_well_test.dart
@@ -160,7 +160,7 @@
           ),
         ),
       );
-      expect(tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, paintsNothing);
+      expect(tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, isNot(paints..circle()));
       await tester.tap(find.byType(InkWell));
       await tester.pump();
       await tester.pump(const Duration(milliseconds: 10));
@@ -171,7 +171,7 @@
       await tester.pump(const Duration(milliseconds: 10));
       expect(
         tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child,
-        keepAlive ? (paints..circle()) : paintsNothing,
+        keepAlive ? (paints..circle()) : isNot(paints..circle()),
       );
     }
     await runTest(true);
diff --git a/packages/flutter/test/rendering/annotated_region_test.dart b/packages/flutter/test/rendering/annotated_region_test.dart
new file mode 100644
index 0000000..d27ad93
--- /dev/null
+++ b/packages/flutter/test/rendering/annotated_region_test.dart
@@ -0,0 +1,128 @@
+// Copyright 2018 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/rendering.dart';
+import 'package:flutter/widgets.dart';
+import 'package:test/test.dart';
+
+void main() {
+  group(AnnotatedRegion, () {
+    test('finds the first value in a OffsetLayer when sized', () {
+      final ContainerLayer containerLayer = new ContainerLayer();
+      final List<OffsetLayer> layers = <OffsetLayer>[
+        new OffsetLayer(offset: Offset.zero),
+        new OffsetLayer(offset: const Offset(0.0, 100.0)),
+        new OffsetLayer(offset: const Offset(0.0, 200.0)),
+      ];
+      int i = 0;
+      for (OffsetLayer layer in layers) {
+        layer.append(new AnnotatedRegionLayer<int>(i, size: const Size(200.0, 100.0)));
+        containerLayer.append(layer);
+        i += 1;
+      }
+
+      expect(containerLayer.find<int>(const Offset(0.0, 1.0)), 0);
+      expect(containerLayer.find<int>(const Offset(0.0, 101.0)), 1);
+      expect(containerLayer.find<int>(const Offset(0.0, 201.0)), 2);
+    });
+
+    test('finds a value within the clip in a ClipRectLayer', () {
+      final ContainerLayer containerLayer = new ContainerLayer();
+      final List<ClipRectLayer> layers = <ClipRectLayer>[
+        new ClipRectLayer(clipRect: new Rect.fromLTRB(0.0, 0.0, 100.0, 100.0)),
+        new ClipRectLayer(clipRect: new Rect.fromLTRB(0.0, 100.0, 100.0, 200.0)),
+        new ClipRectLayer(clipRect: new Rect.fromLTRB(0.0, 200.0, 100.0, 300.0)),
+      ];
+      int i = 0;
+      for (ClipRectLayer layer in layers) {
+        layer.append(new AnnotatedRegionLayer<int>(i));
+        containerLayer.append(layer);
+        i += 1;
+      }
+
+      expect(containerLayer.find<int>(const Offset(0.0, 1.0)), 0);
+      expect(containerLayer.find<int>(const Offset(0.0, 101.0)), 1);
+      expect(containerLayer.find<int>(const Offset(0.0, 201.0)), 2);
+    });
+
+
+    test('finds a value within the clip in a ClipRRectLayer', () {
+      final ContainerLayer containerLayer = new ContainerLayer();
+      final List<ClipRRectLayer> layers = <ClipRRectLayer>[
+        new ClipRRectLayer(clipRRect: new RRect.fromLTRBR(0.0, 0.0, 100.0, 100.0, const Radius.circular(4.0))),
+        new ClipRRectLayer(clipRRect: new RRect.fromLTRBR(0.0, 100.0, 100.0, 200.0, const Radius.circular(4.0))),
+        new ClipRRectLayer(clipRRect: new RRect.fromLTRBR(0.0, 200.0, 100.0, 300.0, const Radius.circular(4.0))),
+      ];
+      int i = 0;
+      for (ClipRRectLayer layer in layers) {
+        layer.append(new AnnotatedRegionLayer<int>(i));
+        containerLayer.append(layer);
+        i += 1;
+      }
+
+      expect(containerLayer.find<int>(const Offset(5.0, 5.0)), 0);
+      expect(containerLayer.find<int>(const Offset(5.0, 105.0)), 1);
+      expect(containerLayer.find<int>(const Offset(5.0, 205.0)), 2);
+    });
+
+    test('finds a value under a TransformLayer', () {
+      final Matrix4 transform = new Matrix4(
+        2.625, 0.0, 0.0, 0.0,
+        0.0, 2.625, 0.0, 0.0,
+        0.0, 0.0, 1.0, 0.0,
+        0.0, 0.0, 0.0, 1.0,
+      );
+      final TransformLayer transformLayer = new TransformLayer(transform: transform);
+      final List<OffsetLayer> layers = <OffsetLayer>[
+        new OffsetLayer(),
+        new OffsetLayer(offset: const Offset(0.0, 100.0)),
+        new OffsetLayer(offset: const Offset(0.0, 200.0)),
+      ];
+      int i = 0;
+      for (OffsetLayer layer in layers) {
+        final AnnotatedRegionLayer<int> annotatedRegionLayer = new AnnotatedRegionLayer<int>(i, size: const Size(100.0, 100.0));
+        layer.append(annotatedRegionLayer);
+        transformLayer.append(layer);
+        i += 1;
+      }
+
+      expect(transformLayer.find<int>(const Offset(0.0, 100.0)), 0);
+      expect(transformLayer.find<int>(const Offset(0.0, 200.0)), 0);
+      expect(transformLayer.find<int>(const Offset(0.0, 270.0)), 1);
+      expect(transformLayer.find<int>(const Offset(0.0, 400.0)), 1);
+      expect(transformLayer.find<int>(const Offset(0.0, 530.0)), 2);
+    });
+
+    test('looks for child AnnotatedRegions before parents', () {
+      final AnnotatedRegionLayer<int> parent = new AnnotatedRegionLayer<int>(1);
+      final AnnotatedRegionLayer<int> child = new AnnotatedRegionLayer<int>(2);
+      final ContainerLayer layer = new ContainerLayer();
+      parent.append(child);
+      layer.append(parent);
+
+      expect(parent.find<int>(Offset.zero), 2);
+    });
+
+    test('looks for correct type', () {
+      final AnnotatedRegionLayer<int> child1 = new AnnotatedRegionLayer<int>(1);
+      final AnnotatedRegionLayer<String> child2 = new AnnotatedRegionLayer<String>('hello');
+      final ContainerLayer layer = new ContainerLayer();
+      layer.append(child2);
+      layer.append(child1);
+
+      expect(layer.find<String>(Offset.zero), 'hello');
+    });
+
+    test('does not clip Layer.find on an AnnotatedRegion with an unrelated type', () {
+      final AnnotatedRegionLayer<int> child = new AnnotatedRegionLayer<int>(1);
+      final AnnotatedRegionLayer<String> parent = new AnnotatedRegionLayer<String>('hello', size: const Size(10.0, 10.0));
+      final ContainerLayer layer = new ContainerLayer();
+      parent.append(child);
+      layer.append(parent);
+
+      expect(layer.find<int>(const Offset(100.0, 100.0)), 1);
+    });
+  });
+}
+
diff --git a/packages/flutter/test/rendering/recording_canvas.dart b/packages/flutter/test/rendering/recording_canvas.dart
index 952fbdc..9803b89 100644
--- a/packages/flutter/test/rendering/recording_canvas.dart
+++ b/packages/flutter/test/rendering/recording_canvas.dart
@@ -136,6 +136,11 @@
   }
 
   @override
+  void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset, {Rect childPaintBounds}) {
+    painter(this, offset);
+  }
+
+  @override
   void noSuchMethod(Invocation invocation) { }
 }
 
diff --git a/packages/flutter/test/widgets/annotated_region_test.dart b/packages/flutter/test/widgets/annotated_region_test.dart
new file mode 100644
index 0000000..144c6ca
--- /dev/null
+++ b/packages/flutter/test/widgets/annotated_region_test.dart
@@ -0,0 +1,21 @@
+// Copyright 2018 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/rendering.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+  testWidgets('provides a value to the layer tree', (WidgetTester tester) async {
+    await tester.pumpWidget(
+      const AnnotatedRegion<int>(
+        child: const SizedBox(width: 100.0, height: 100.0),
+        value: 1,
+      ),
+    );
+    final List<Layer> layers = tester.layers;
+    final AnnotatedRegionLayer<int> layer = layers.firstWhere((Layer layer) => layer is AnnotatedRegionLayer<int>);
+    expect(layer.value, 1);
+  });
+}