Support SemanticsFlag for Header (#15255)

diff --git a/examples/flutter_gallery/lib/gallery/home.dart b/examples/flutter_gallery/lib/gallery/home.dart
index 694b89b..cfc8372 100644
--- a/examples/flutter_gallery/lib/gallery/home.dart
+++ b/examples/flutter_gallery/lib/gallery/home.dart
@@ -152,7 +152,10 @@
               child: new SafeArea(
                 top: false,
                 bottom: false,
-                child: new Text(galleryItem.category, style: headerStyle),
+                child: new Semantics(
+                  header: true,
+                  child: new Text(galleryItem.category, style: headerStyle),
+                ),
               ),
             ),
           )
diff --git a/packages/flutter/lib/src/rendering/custom_paint.dart b/packages/flutter/lib/src/rendering/custom_paint.dart
index 79beea4..a6dc53b 100644
--- a/packages/flutter/lib/src/rendering/custom_paint.dart
+++ b/packages/flutter/lib/src/rendering/custom_paint.dart
@@ -817,6 +817,21 @@
     if (properties.button != null) {
       config.isButton = properties.button;
     }
+    if (properties.textField != null) {
+      config.isTextField = properties.textField;
+    }
+    if (properties.focused != null) {
+      config.isFocused = properties.focused;
+    }
+    if (properties.enabled != null) {
+      config.isEnabled = properties.enabled;
+    }
+    if (properties.inMutuallyExclusiveGroup != null) {
+      config.isInMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup;
+    }
+    if (properties.header != null) {
+      config.isHeader = properties.header;
+    }
     if (properties.label != null) {
       config.label = properties.label;
     }
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart
index a4d9946..fc685b6 100644
--- a/packages/flutter/lib/src/rendering/proxy_box.dart
+++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -3015,6 +3015,10 @@
     bool checked,
     bool selected,
     bool button,
+    bool header,
+    bool textField,
+    bool focused,
+    bool inMutuallyExclusiveGroup,
     String label,
     String value,
     String increasedValue,
@@ -3045,6 +3049,10 @@
        _checked = checked,
        _selected = selected,
        _button = button,
+       _header = header,
+       _textField = textField,
+       _focused = focused,
+       _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
        _label = label,
        _value = value,
        _increasedValue = increasedValue,
@@ -3152,6 +3160,47 @@
     markNeedsSemanticsUpdate();
   }
 
+  /// If non-null, sets the [SemanticsNode.isHeader] semantic to the given value.
+  bool get header => _header;
+  bool _header;
+  set header(bool value) {
+    if (header == value)
+      return;
+    _header = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.isTextField] semantic to the given value.
+  bool get textField => _textField;
+  bool _textField;
+  set textField(bool value) {
+    if (textField == value)
+      return;
+    _textField = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.isFocused] semantic to the given value.
+  bool get focused => _focused;
+  bool _focused;
+  set focused(bool value) {
+    if (focused == value)
+      return;
+    _focused = value;
+    markNeedsSemanticsUpdate();
+  }
+
+  /// If non-null, sets the [SemanticsNode.isInMutuallyExclusiveGroup] semantic
+  /// to the given value.
+  bool get inMutuallyExclusiveGroup => _inMutuallyExclusiveGroup;
+  bool _inMutuallyExclusiveGroup;
+  set inMutuallyExclusiveGroup(bool value) {
+    if (inMutuallyExclusiveGroup == value)
+      return;
+    _inMutuallyExclusiveGroup = value;
+    markNeedsSemanticsUpdate();
+  }
+
   /// If non-null, sets the [SemanticsNode.label] semantic to the given value.
   ///
   /// The reading direction is given by [textDirection].
@@ -3581,6 +3630,14 @@
       config.isSelected = selected;
     if (button != null)
       config.isButton = button;
+    if (header != null)
+      config.isHeader = header;
+    if (textField != null)
+      config.isTextField = textField;
+    if (focused != null)
+      config.isFocused = focused;
+    if (inMutuallyExclusiveGroup != null)
+      config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup;
     if (label != null)
       config.label = label;
     if (value != null)
diff --git a/packages/flutter/lib/src/semantics/semantics.dart b/packages/flutter/lib/src/semantics/semantics.dart
index 2318fdd..812c769 100644
--- a/packages/flutter/lib/src/semantics/semantics.dart
+++ b/packages/flutter/lib/src/semantics/semantics.dart
@@ -315,6 +315,10 @@
     this.checked,
     this.selected,
     this.button,
+    this.header,
+    this.textField,
+    this.focused,
+    this.inMutuallyExclusiveGroup,
     this.label,
     this.value,
     this.increasedValue,
@@ -366,6 +370,35 @@
   /// is focused.
   final bool button;
 
+  /// If non-null, indicates that this subtree represents a header.
+  ///
+  /// A header divides into sections. For example, an address book application
+  /// might define headers A, B, C, etc. to divide the list of alphabetically
+  /// sorted contacts into sections.
+  final bool header;
+
+  /// If non-null, indicates that this subtree represents a text field.
+  ///
+  /// TalkBack/VoiceOver provide special affordances to enter text into a
+  /// text field.
+  final bool textField;
+
+  /// If non-null, whether the node currently holds input focus.
+  ///
+  /// At most one node in the tree should hold input focus at any point in time.
+  ///
+  /// Input focus (indicates that the node will receive keyboard events) is not
+  /// to be confused with accessibility focus. Accessibility focus is the
+  /// green/black rectangular that TalkBack/VoiceOver on the screen and is
+  /// separate from input focus.
+  final bool focused;
+
+  /// If non-null, whether a semantic node is in a mutually exclusive group.
+  ///
+  /// For example, a radio button is in a mutually exclusive group because only
+  /// one radio button in that group can be marked as [checked].
+  final bool inMutuallyExclusiveGroup;
+
   /// Provides a textual description of the widget.
   ///
   /// If a label is provided, there must either by an ambient [Directionality]
@@ -2365,6 +2398,12 @@
     _setFlag(SemanticsFlag.isButton, value);
   }
 
+  /// Whether the owning [RenderObject] is a header (true) or not (false).
+  bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
+  set isHeader(bool value) {
+    _setFlag(SemanticsFlag.isHeader, value);
+  }
+
   /// Whether the owning [RenderObject] is a text field.
   bool get isTextField => _hasFlag(SemanticsFlag.isTextField);
   set isTextField(bool value) {
diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart
index 7f1b565..a7cad3a 100644
--- a/packages/flutter/lib/src/widgets/basic.dart
+++ b/packages/flutter/lib/src/widgets/basic.dart
@@ -4847,6 +4847,10 @@
     bool checked,
     bool selected,
     bool button,
+    bool header,
+    bool textField,
+    bool focused,
+    bool inMutuallyExclusiveGroup,
     String label,
     String value,
     String increasedValue,
@@ -4881,6 +4885,10 @@
       checked: checked,
       selected: selected,
       button: button,
+      header: header,
+      textField: textField,
+      focused: focused,
+      inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
       label: label,
       value: value,
       increasedValue: increasedValue,
@@ -4960,6 +4968,10 @@
       checked: properties.checked,
       selected: properties.selected,
       button: properties.button,
+      header: properties.header,
+      textField: properties.textField,
+      focused: properties.focused,
+      inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
       label: properties.label,
       value: properties.value,
       increasedValue: properties.increasedValue,
diff --git a/packages/flutter/test/widgets/custom_painter_test.dart b/packages/flutter/test/widgets/custom_painter_test.dart
index 8484293..04d6fa3 100644
--- a/packages/flutter/test/widgets/custom_painter_test.dart
+++ b/packages/flutter/test/widgets/custom_painter_test.dart
@@ -3,6 +3,7 @@
 // found in the LICENSE file.
 
 import 'dart:async';
+import 'dart:ui';
 
 import 'package:flutter_test/flutter_test.dart';
 import 'package:flutter/rendering.dart';
@@ -397,6 +398,52 @@
     semantics.dispose();
   });
 
+  testWidgets('Supports all flags', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    await tester.pumpWidget(new CustomPaint(
+      painter: new _PainterWithSemantics(
+        semantics: new CustomPainterSemantics(
+          key: const ValueKey<int>(1),
+          rect: new Rect.fromLTRB(1.0, 2.0, 3.0, 4.0),
+          properties: const SemanticsProperties(
+            enabled: true,
+            checked: true,
+            selected: true,
+            button: true,
+            textField: true,
+            focused: true,
+            inMutuallyExclusiveGroup: true,
+            header: true,
+          ),
+        ),
+      ),
+    ));
+
+    const int expectedId = 2;
+    final TestSemantics expectedSemantics = new TestSemantics.root(
+      children: <TestSemantics>[
+        new TestSemantics.rootChild(
+            id: 1,
+            previousNodeId: -1,
+            nextNodeId: expectedId,
+            children: <TestSemantics>[
+              new TestSemantics.rootChild(
+                id: expectedId,
+                rect: TestSemantics.fullScreen,
+                flags: SemanticsFlag.values.values.toList(),
+                previousNodeId: 1,
+                nextNodeId: -1,
+              ),
+            ]
+        ),
+      ],
+    );
+    expect(semantics, hasSemantics(expectedSemantics, ignoreRect: true, ignoreTransform: true));
+
+    semantics.dispose();
+  });
+
   group('diffing', () {
     testWidgets('complains about duplicate keys', (WidgetTester tester) async {
       final SemanticsTester semanticsTester = new SemanticsTester(tester);
diff --git a/packages/flutter/test/widgets/semantics_test.dart b/packages/flutter/test/widgets/semantics_test.dart
index ae925ced..d6ad7cb 100644
--- a/packages/flutter/test/widgets/semantics_test.dart
+++ b/packages/flutter/test/widgets/semantics_test.dart
@@ -449,6 +449,39 @@
     semantics.dispose();
   });
 
+  testWidgets('Semantics widget supports all flags', (WidgetTester tester) async {
+    final SemanticsTester semantics = new SemanticsTester(tester);
+
+    await tester.pumpWidget(
+        new Semantics(
+          container: true,
+          // flags
+          enabled: true,
+          checked: true,
+          selected: true,
+          button: true,
+          textField: true,
+          focused: true,
+          inMutuallyExclusiveGroup: true,
+          header: true,
+        )
+    );
+
+    final TestSemantics expectedSemantics = new TestSemantics.root(
+      children: <TestSemantics>[
+        new TestSemantics.rootChild(
+          rect: TestSemantics.fullScreen,
+          flags: SemanticsFlag.values.values.toList(),
+          previousNodeId: -1,
+          nextNodeId: -1,
+        ),
+      ],
+    );
+    expect(semantics, hasSemantics(expectedSemantics, ignoreId: true));
+
+    semantics.dispose();
+  });
+
   testWidgets('Actions can be replaced without triggering semantics update', (WidgetTester tester) async {
     final SemanticsTester semantics = new SemanticsTester(tester);
     int semanticsUpdateCount = 0;