Add enableFeedback param to MaterialButton, RawMaterialButton and IconButton (#41972)

* Wire enableFeedback parameter through MaterialButton, RawMaterialButton, and IconButton.

Co-Authored-By: Shi-Hao Hong <shihaohong@google.com>
diff --git a/packages/flutter/lib/src/material/button.dart b/packages/flutter/lib/src/material/button.dart
index 4b4c9cc..daf89a0 100644
--- a/packages/flutter/lib/src/material/button.dart
+++ b/packages/flutter/lib/src/material/button.dart
@@ -60,6 +60,7 @@
     this.autofocus = false,
     MaterialTapTargetSize materialTapTargetSize,
     this.child,
+    this.enableFeedback = true,
   }) : materialTapTargetSize = materialTapTargetSize ?? MaterialTapTargetSize.padded,
        assert(shape != null),
        assert(elevation != null && elevation >= 0.0),
@@ -259,6 +260,16 @@
   /// Defaults to [Clip.none], and must not be null.
   final Clip clipBehavior;
 
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
   @override
   _RawMaterialButtonState createState() => _RawMaterialButtonState();
 }
@@ -367,6 +378,7 @@
           onHover: _handleHoveredChanged,
           onTap: widget.onPressed,
           onLongPress: widget.onLongPress,
+          enableFeedback: widget.enableFeedback,
           customBorder: effectiveShape,
           child: IconTheme.merge(
             data: IconThemeData(color: effectiveTextColor),
diff --git a/packages/flutter/lib/src/material/icon_button.dart b/packages/flutter/lib/src/material/icon_button.dart
index 6fe788d..ba90468 100644
--- a/packages/flutter/lib/src/material/icon_button.dart
+++ b/packages/flutter/lib/src/material/icon_button.dart
@@ -151,6 +151,7 @@
     this.focusNode,
     this.autofocus = false,
     this.tooltip,
+    this.enableFeedback = true,
   }) : assert(iconSize != null),
        assert(padding != null),
        assert(alignment != null),
@@ -269,6 +270,16 @@
   /// used for accessibility.
   final String tooltip;
 
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
   @override
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterial(context));
@@ -314,6 +325,7 @@
         autofocus: autofocus,
         canRequestFocus: onPressed != null,
         onTap: onPressed,
+        enableFeedback: enableFeedback,
         child: result,
         focusColor: focusColor ?? Theme.of(context).focusColor,
         hoverColor: hoverColor ?? Theme.of(context).hoverColor,
diff --git a/packages/flutter/lib/src/material/material_button.dart b/packages/flutter/lib/src/material/material_button.dart
index 5b12358..20a2b04 100644
--- a/packages/flutter/lib/src/material/material_button.dart
+++ b/packages/flutter/lib/src/material/material_button.dart
@@ -77,6 +77,7 @@
     this.animationDuration,
     this.minWidth,
     this.height,
+    this.enableFeedback = true,
     this.child,
   }) : assert(clipBehavior != null),
        assert(autofocus != null),
@@ -355,6 +356,16 @@
   /// Defaults to the value from the current [ButtonTheme].
   final double height;
 
+  /// Whether detected gestures should provide acoustic and/or haptic feedback.
+  ///
+  /// For example, on Android a tap will produce a clicking sound and a
+  /// long-press will produce a short vibration, when feedback is enabled.
+  ///
+  /// See also:
+  ///
+  ///  * [Feedback] for providing platform-specific feedback to certain actions.
+  final bool enableFeedback;
+
   @override
   Widget build(BuildContext context) {
     final ThemeData theme = Theme.of(context);
@@ -363,6 +374,7 @@
     return RawMaterialButton(
       onPressed: onPressed,
       onLongPress: onLongPress,
+      enableFeedback: enableFeedback,
       onHighlightChanged: onHighlightChanged,
       fillColor: buttonTheme.getFillColor(this),
       textStyle: theme.textTheme.button.copyWith(color: buttonTheme.getTextColor(this)),
diff --git a/packages/flutter/test/material/icon_button_test.dart b/packages/flutter/test/material/icon_button_test.dart
index 247441f..8cf16f3 100644
--- a/packages/flutter/test/material/icon_button_test.dart
+++ b/packages/flutter/test/material/icon_button_test.dart
@@ -10,6 +10,7 @@
 
 import '../rendering/mock_canvas.dart';
 import '../widgets/semantics_tester.dart';
+import 'feedback_tester.dart';
 
 class MockOnPressedFunction implements Function {
   int called = 0;
@@ -398,6 +399,74 @@
     expect(focusNode1.hasPrimaryFocus, isTrue);
     expect(focusNode2.hasPrimaryFocus, isFalse);
   });
+
+  group('feedback', () {
+    FeedbackTester feedback;
+
+    setUp(() {
+      feedback = FeedbackTester();
+    });
+
+    tearDown(() {
+      feedback?.dispose();
+    });
+
+    testWidgets('IconButton with disabled feedback', (WidgetTester tester) async {
+      await tester.pumpWidget(Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Center(
+            child: IconButton(
+              onPressed: () {},
+              enableFeedback: false,
+              icon: const Icon(Icons.link),
+            ),
+          ),
+        ),
+      ));
+      await tester.tap(find.byType(IconButton), pointer: 1);
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 0);
+      expect(feedback.hapticCount, 0);
+    });
+
+    testWidgets('IconButton with enabled feedback', (WidgetTester tester) async {
+      await tester.pumpWidget(Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Center(
+            child: IconButton(
+              onPressed: () {},
+              enableFeedback: true,
+              icon: const Icon(Icons.link),
+            ),
+          ),
+        ),
+      ));
+      await tester.tap(find.byType(IconButton), pointer: 1);
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 1);
+      expect(feedback.hapticCount, 0);
+    });
+
+    testWidgets('IconButton with enabled feedback by default', (WidgetTester tester) async {
+      await tester.pumpWidget(Material(
+        child: Directionality(
+          textDirection: TextDirection.ltr,
+          child: Center(
+            child: IconButton(
+              onPressed: () {},
+              icon: const Icon(Icons.link),
+            ),
+          ),
+        ),
+      ));
+      await tester.tap(find.byType(IconButton), pointer: 1);
+      await tester.pump(const Duration(seconds: 1));
+      expect(feedback.clickSoundCount, 1);
+      expect(feedback.hapticCount, 0);
+    });
+  });
 }
 
 Widget wrap({ Widget child }) {