Extract common PlatformView functionality: Painting and Semantics (#36955)

* painting and semantics

* more comments

* fixing ci

* review fixes

* add assert for id

* rename custom layer factory to layer builder

* review updates

* partial review fixes

* some doc updates

* more doc updates

* only expose getter for id in PlatformViewController

* doc updates/removing all the  references

* remove extra

* more doc updates

* some doc updates

* more doc fixes

* review fixes
diff --git a/packages/flutter/lib/src/rendering/platform_view.dart b/packages/flutter/lib/src/rendering/platform_view.dart
index 1409015..65b8e6c 100644
--- a/packages/flutter/lib/src/rendering/platform_view.dart
+++ b/packages/flutter/lib/src/rendering/platform_view.dart
@@ -725,3 +725,69 @@
   bool isSinglePointerAction(PointerEvent event) =>
       !(event is PointerDownEvent) && !(event is PointerUpEvent);
 }
+
+/// A render object for embedding a platform view.
+///
+/// [PlatformViewRenderBox] presents a platform view by adding a [PlatformViewLayer] layer, integrates it with the gesture arenas system
+/// and adds relevant semantic nodes to the semantics tree.
+class PlatformViewRenderBox extends RenderBox {
+
+  /// Creating a render object for a [PlatformViewSurface].
+  ///
+  /// The `controller` parameter must not be null.
+  PlatformViewRenderBox({
+    @required PlatformViewController controller,
+
+  }) : assert(controller != null && controller.viewId != null && controller.viewId > -1),
+       _controller = controller;
+
+  /// Sets the [controller] for this render object.
+  ///
+  /// This value must not be null, and setting it to a new value will result in a repaint.
+  set controller(PlatformViewController controller) {
+    assert(controller != null);
+    assert(controller.viewId != null && controller.viewId > -1);
+
+    if ( _controller == controller) {
+      return;
+    }
+    final bool needsSemanticsUpdate = _controller.viewId != controller.viewId;
+     _controller = controller;
+     markNeedsPaint();
+    if (needsSemanticsUpdate) {
+      markNeedsSemanticsUpdate();
+    }
+  }
+
+  PlatformViewController _controller;
+
+  @override
+  bool get sizedByParent => true;
+
+  @override
+  bool get alwaysNeedsCompositing => true;
+
+  @override
+  bool get isRepaintBoundary => true;
+
+  @override
+  void performResize() {
+    size = constraints.biggest;
+  }
+
+  @override
+  void paint(PaintingContext context, Offset offset) {
+    assert(_controller.viewId != null);
+    context.addLayer(PlatformViewLayer(
+            rect: offset & size,
+            viewId: _controller.viewId));
+  }
+
+  @override
+  void describeSemanticsConfiguration (SemanticsConfiguration config) {
+    super.describeSemanticsConfiguration(config);
+    assert(_controller.viewId != null);
+    config.isSemanticBoundary = true;
+    config.platformViewId = _controller.viewId;
+  }
+}
diff --git a/packages/flutter/lib/src/services/platform_views.dart b/packages/flutter/lib/src/services/platform_views.dart
index 790fa5c..28e4ead 100644
--- a/packages/flutter/lib/src/services/platform_views.dart
+++ b/packages/flutter/lib/src/services/platform_views.dart
@@ -713,3 +713,16 @@
     await SystemChannels.platform_views.invokeMethod<void>('dispose', id);
   }
 }
+
+/// An interface for a controlling a single platform view.
+///
+/// Used by [PlatformViewSurface] to interface with the platform view it embeds.
+abstract class PlatformViewController {
+
+  /// The viewId associated with this controller.
+  ///
+  /// The viewId should always be unique and non-negative. And it must not be null.
+  ///
+  /// See also [PlatformViewRegistry] which is a helper for managing platform view ids.
+  int get viewId;
+}
diff --git a/packages/flutter/lib/src/widgets/platform_view.dart b/packages/flutter/lib/src/widgets/platform_view.dart
index 463d572..d727fa1 100644
--- a/packages/flutter/lib/src/widgets/platform_view.dart
+++ b/packages/flutter/lib/src/widgets/platform_view.dart
@@ -580,3 +580,45 @@
     renderObject.updateGestureRecognizers(gestureRecognizers);
   }
 }
+
+/// Integrates a platform view with Flutter's compositor, touch, and semantics subsystems.
+///
+/// The compositor integration is done by adding a [PlatformViewLayer] to the layer tree. [PlatformViewLayer]
+/// isn't supported on all platforms (e.g on Android platform views are composited using a [TextureLayer]).
+/// Custom Flutter embedders can support [PlatformViewLayer]s by implementing a SystemCompositor.
+///
+/// The widget fills all available space, the parent of this object must provide bounded layout
+/// constraints.
+///
+/// If the associated platform view is not created the [PlatformViewSurface] does not paint any contents.
+///
+/// See also:
+/// * [AndroidView] which embeds an Android platform view in the widget hierarchy.
+/// * [UIKitView] which embeds an iOS platform view in the widget hierarchy.
+// TODO(amirh): Link to the embedder's system compositor documentation once available.
+class PlatformViewSurface extends LeafRenderObjectWidget {
+
+  /// Construct a `PlatformViewSurface`.
+  ///
+  /// The [controller] must not be null.
+  const PlatformViewSurface({
+    @required this.controller,
+  }) : assert(controller != null);
+
+  /// The controller for the platform view integrated by this [PlatformViewSurface].
+  ///
+  /// [PlatformViewController] is used for dispatching touch events to the platform view.
+  /// [PlatformViewController.viewId] identifies the platform view whose contents are painted by this widget.
+  final PlatformViewController controller;
+
+  @override
+  RenderObject createRenderObject(BuildContext context) {
+    return PlatformViewRenderBox(controller: controller);
+  }
+
+  @override
+  void updateRenderObject(BuildContext context, PlatformViewRenderBox renderObject) {
+    renderObject
+      ..controller = controller;
+  }
+}
diff --git a/packages/flutter/test/rendering/platform_view_test.dart b/packages/flutter/test/rendering/platform_view_test.dart
new file mode 100644
index 0000000..71110c9
--- /dev/null
+++ b/packages/flutter/test/rendering/platform_view_test.dart
@@ -0,0 +1,60 @@
+// Copyright 2019 The Flutter 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_test/flutter_test.dart';
+import 'package:flutter/material.dart';
+import '../services/fake_platform_views.dart';
+import 'rendering_tester.dart';
+
+void main() {
+
+  group('PlatformViewRenderBox', () {
+    FakePlatformViewController fakePlatformViewController;
+    PlatformViewRenderBox platformViewRenderBox;
+    setUp((){
+      fakePlatformViewController = FakePlatformViewController(0);
+      platformViewRenderBox = PlatformViewRenderBox(controller: fakePlatformViewController);
+    });
+
+    test('layout should size to max constraint', () {
+      layout(platformViewRenderBox);
+      platformViewRenderBox.layout(const BoxConstraints(minWidth: 50, minHeight: 50, maxWidth: 100, maxHeight: 100));
+      expect(platformViewRenderBox.size, const Size(100, 100));
+    });
+
+    test('send semantics update if id is changed', (){
+      final RenderObject tree = RenderConstrainedBox(
+        additionalConstraints: const BoxConstraints.tightFor(height: 20.0, width: 20.0),
+        child: platformViewRenderBox,
+      );
+      int semanticsUpdateCount = 0;
+      final SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
+          listener: () {
+            ++semanticsUpdateCount;
+          }
+      );
+      layout(tree, phase: EnginePhase.flushSemantics);
+      // Initial semantics update
+      expect(semanticsUpdateCount, 1);
+
+      semanticsUpdateCount = 0;
+
+      // Request semantics update even though nothing changed.
+      platformViewRenderBox.markNeedsSemanticsUpdate();
+      pumpFrame(phase: EnginePhase.flushSemantics);
+      expect(semanticsUpdateCount, 0);
+
+      semanticsUpdateCount = 0;
+
+      final FakePlatformViewController updatedFakePlatformViewController = FakePlatformViewController(10);
+      platformViewRenderBox.controller = updatedFakePlatformViewController;
+      pumpFrame(phase: EnginePhase.flushSemantics);
+      // Update id should update the semantics.
+      expect(semanticsUpdateCount, 1);
+
+      semanticsHandle.dispose();
+    });
+  });
+}
diff --git a/packages/flutter/test/services/fake_platform_views.dart b/packages/flutter/test/services/fake_platform_views.dart
index bc49eae..9e780e9 100644
--- a/packages/flutter/test/services/fake_platform_views.dart
+++ b/packages/flutter/test/services/fake_platform_views.dart
@@ -9,6 +9,19 @@
 import 'package:flutter/painting.dart';
 import 'package:flutter/services.dart';
 
+/// Used in internal testing.
+class FakePlatformViewController extends PlatformViewController {
+
+  FakePlatformViewController(int id) {
+    _id = id;
+  }
+
+  int _id;
+
+  @override
+  int get viewId => _id;
+}
+
 class FakeAndroidPlatformViewsController {
   FakeAndroidPlatformViewsController() {
     SystemChannels.platform_views.setMockMethodCallHandler(_onMethodCall);
diff --git a/packages/flutter/test/widgets/platform_view_test.dart b/packages/flutter/test/widgets/platform_view_test.dart
index cadfa7f..0c141e5 100644
--- a/packages/flutter/test/widgets/platform_view_test.dart
+++ b/packages/flutter/test/widgets/platform_view_test.dart
@@ -1667,4 +1667,21 @@
       handle.dispose();
     });
   });
+
+  group('Common PlatformView', () {
+    FakePlatformViewController controller;
+
+    setUp((){
+      controller = FakePlatformViewController(0);
+    });
+
+    testWidgets('PlatformViewSurface should create platform view layer', (WidgetTester tester) async {
+      final PlatformViewSurface surface = PlatformViewSurface(controller: controller);
+      await tester.pumpWidget(surface);
+      final PlatformViewLayer layer = tester.layers.firstWhere((Layer layer){
+        return layer is PlatformViewLayer;
+      });
+      expect(layer, isNotNull);
+    });
+  });
 }