Material allows "select all" when not collapsed (#32950)

This PR enables "Select all" on MaterialTextSelection when text is partially selected.
diff --git a/bin/internal/goldens.version b/bin/internal/goldens.version
index 00adb77..ad05202 100644
--- a/bin/internal/goldens.version
+++ b/bin/internal/goldens.version
@@ -1 +1 @@
-25ab3c95b1ac02a41973c05f96e664370857ce55
+d79941e3d9c6e761189ac8aefa3a40c7fb98413e
diff --git a/packages/flutter/lib/src/material/text_selection.dart b/packages/flutter/lib/src/material/text_selection.dart
index cf33e15..b50ff0c 100644
--- a/packages/flutter/lib/src/material/text_selection.dart
+++ b/packages/flutter/lib/src/material/text_selection.dart
@@ -212,6 +212,15 @@
     assert(type != null);
     return null;
   }
+
+  @override
+  bool canSelectAll(TextSelectionDelegate delegate) {
+    // Android allows SelectAll when selection is not collapsed, unless
+    // everything has already been selected.
+    final TextEditingValue value = delegate.textEditingValue;
+    return value.text.isNotEmpty &&
+      !(value.selection.start == 0 && value.selection.end == value.text.length);
+  }
 }
 
 /// Text selection controls that follow the Material Design specification.
diff --git a/packages/flutter/test/cupertino/text_selection_test.dart b/packages/flutter/test/cupertino/text_selection_test.dart
new file mode 100644
index 0000000..dc884da
--- /dev/null
+++ b/packages/flutter/test/cupertino/text_selection_test.dart
@@ -0,0 +1,65 @@
+// 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_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter/cupertino.dart';
+
+void main() {
+  group('canSelectAll', () {
+    Widget createEditableText({
+      Key key,
+      String text,
+      TextSelection selection,
+    }) {
+      final TextEditingController controller = TextEditingController(text: text)
+        ..selection = selection ?? const TextSelection.collapsed(offset: -1);
+      return CupertinoApp(
+        home: EditableText(
+          key: key,
+          controller: controller,
+          focusNode: FocusNode(),
+          style: const TextStyle(),
+          cursorColor: const Color.fromARGB(0, 0, 0, 0),
+          backgroundCursorColor: const Color.fromARGB(0, 0, 0, 0),
+        )
+      );
+    }
+
+    testWidgets('should return false when there is no text', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(key: key));
+      expect(cupertinoTextSelectionControls.canSelectAll(key.currentState), false);
+    });
+
+    testWidgets('should return true when there is text and collapsed selection', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(
+        key: key,
+        text: '123',
+      ));
+      expect(cupertinoTextSelectionControls.canSelectAll(key.currentState), true);
+    });
+
+    testWidgets('should return false when there is text and partial uncollapsed selection', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(
+        key: key,
+        text: '123',
+        selection: const TextSelection(baseOffset: 1, extentOffset: 2),
+      ));
+      expect(cupertinoTextSelectionControls.canSelectAll(key.currentState), false);
+    });
+
+    testWidgets('should return false when there is text and full selection', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(
+        key: key,
+        text: '123',
+        selection: const TextSelection(baseOffset: 0, extentOffset: 3),
+      ));
+      expect(cupertinoTextSelectionControls.canSelectAll(key.currentState), false);
+    });
+  });
+}
diff --git a/packages/flutter/test/material/text_field_test.dart b/packages/flutter/test/material/text_field_test.dart
index 86a7879..6f46196 100644
--- a/packages/flutter/test/material/text_field_test.dart
+++ b/packages/flutter/test/material/text_field_test.dart
@@ -483,7 +483,7 @@
     await expectLater(
       // The toolbar exists in the Overlay above the MaterialApp.
       find.byType(Overlay),
-      matchesGoldenFile('text_field_opacity_test.0.1.png'),
+      matchesGoldenFile('text_field_opacity_test.0.2.png'),
       skip: !Platform.isLinux,
     );
   });
@@ -4921,8 +4921,8 @@
         const TextSelection(baseOffset: 8, extentOffset: 12),
       );
 
-      // Selected text shows 3 toolbar buttons.
-      expect(find.byType(FlatButton), findsNWidgets(3));
+      // Selected text shows 4 toolbar buttons: cut, copy, paste, select all
+      expect(find.byType(FlatButton), findsNWidgets(4));
     },
   );
 
@@ -5086,8 +5086,8 @@
         const TextSelection(baseOffset: 0, extentOffset: 7),
       );
 
-      // Collapsed toolbar shows 3 buttons.
-      expect(find.byType(FlatButton), findsNWidgets(3));
+      // Collapsed toolbar shows 4 buttons: cut, copy, paste, select all
+      expect(find.byType(FlatButton), findsNWidgets(4));
     },
   );
 
diff --git a/packages/flutter/test/material/text_selection_test.dart b/packages/flutter/test/material/text_selection_test.dart
new file mode 100644
index 0000000..a143a00
--- /dev/null
+++ b/packages/flutter/test/material/text_selection_test.dart
@@ -0,0 +1,65 @@
+// 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_test/flutter_test.dart';
+import 'package:flutter/widgets.dart';
+import 'package:flutter/material.dart';
+
+void main() {
+  group('canSelectAll', () {
+    Widget createEditableText({
+      Key key,
+      String text,
+      TextSelection selection,
+    }) {
+      final TextEditingController controller = TextEditingController(text: text)
+        ..selection = selection ?? const TextSelection.collapsed(offset: -1);
+      return MaterialApp(
+        home: EditableText(
+          key: key,
+          controller: controller,
+          focusNode: FocusNode(),
+          style: const TextStyle(),
+          cursorColor: Colors.black,
+          backgroundCursorColor: Colors.black,
+        )
+      );
+    }
+
+    testWidgets('should return false when there is no text', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(key: key));
+      expect(materialTextSelectionControls.canSelectAll(key.currentState), false);
+    });
+
+    testWidgets('should return true when there is text and collapsed selection', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(
+        key: key,
+        text: '123',
+      ));
+      expect(materialTextSelectionControls.canSelectAll(key.currentState), true);
+    });
+
+    testWidgets('should return true when there is text and partial uncollapsed selection', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(
+        key: key,
+        text: '123',
+        selection: const TextSelection(baseOffset: 1, extentOffset: 2),
+      ));
+      expect(materialTextSelectionControls.canSelectAll(key.currentState), true);
+    });
+
+    testWidgets('should return false when there is text and full selection', (WidgetTester tester) async {
+      final GlobalKey<EditableTextState> key = GlobalKey();
+      await tester.pumpWidget(createEditableText(
+        key: key,
+        text: '123',
+        selection: const TextSelection(baseOffset: 0, extentOffset: 3),
+      ));
+      expect(materialTextSelectionControls.canSelectAll(key.currentState), false);
+    });
+  });
+}