Audit use of defaultTargetPlatform (#36871)

diff --git a/packages/flutter/lib/src/cupertino/picker.dart b/packages/flutter/lib/src/cupertino/picker.dart
index 63a7efb..080cca0 100644
--- a/packages/flutter/lib/src/cupertino/picker.dart
+++ b/packages/flutter/lib/src/cupertino/picker.dart
@@ -218,8 +218,18 @@
   void _handleSelectedItemChanged(int index) {
     // Only the haptic engine hardware on iOS devices would produce the
     // intended effects.
-    if (defaultTargetPlatform == TargetPlatform.iOS
-        && index != _lastHapticIndex) {
+    bool hasSuitableHapticHardware;
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.iOS:
+        hasSuitableHapticHardware = true;
+        break;
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        hasSuitableHapticHardware = false;
+        break;
+    }
+    assert(hasSuitableHapticHardware != null);
+    if (hasSuitableHapticHardware && index != _lastHapticIndex) {
       _lastHapticIndex = index;
       HapticFeedback.selectionClick();
     }
diff --git a/packages/flutter/lib/src/cupertino/switch.dart b/packages/flutter/lib/src/cupertino/switch.dart
index 39c2dec..de6ef64 100644
--- a/packages/flutter/lib/src/cupertino/switch.dart
+++ b/packages/flutter/lib/src/cupertino/switch.dart
@@ -425,7 +425,7 @@
   }
 
   void _emitVibration() {
-    switch(defaultTargetPlatform) {
+    switch (defaultTargetPlatform) {
       case TargetPlatform.iOS:
         HapticFeedback.lightImpact();
         break;
diff --git a/packages/flutter/lib/src/foundation/platform.dart b/packages/flutter/lib/src/foundation/platform.dart
index 90b618a..908bf3e 100644
--- a/packages/flutter/lib/src/foundation/platform.dart
+++ b/packages/flutter/lib/src/foundation/platform.dart
@@ -11,11 +11,11 @@
 /// This is the default value of [ThemeData.platform] (hence the name). Widgets
 /// from the material library should use [Theme.of] to determine the current
 /// platform for styling purposes, rather than using [defaultTargetPlatform].
-/// However, if there is widget behavior that depends on the actual underlying
-/// platform, then depending on [defaultTargetPlatform] makes sense.
-/// [dart.io.Platform.environment] should be used directly only when it's
-/// critical to actually know the current platform, without any overrides
-/// possible (for example, when a system API is about to be called).
+/// Widgets and render objects at lower layers that try to emulate the
+/// underlying platform can depend on [defaultTargetPlatform] directly. The
+/// [dart.io.Platform] object should only be used directly when it's critical to
+/// actually know the current platform, without any overrides possible (for
+/// example, when a system API is about to be called).
 ///
 /// In a test environment, the platform returned is [TargetPlatform.android]
 /// regardless of the host platform. (Android was chosen because the tests were
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart
index 28d072d..c4a4712 100644
--- a/packages/flutter/lib/src/material/app_bar.dart
+++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -384,11 +384,11 @@
   @override
   final Size preferredSize;
 
-  bool _getEffectiveCenterTitle(ThemeData themeData) {
+  bool _getEffectiveCenterTitle(ThemeData theme) {
     if (centerTitle != null)
       return centerTitle;
-    assert(themeData.platform != null);
-    switch (themeData.platform) {
+    assert(theme.platform != null);
+    switch (theme.platform) {
       case TargetPlatform.android:
       case TargetPlatform.fuchsia:
         return false;
@@ -417,7 +417,7 @@
   Widget build(BuildContext context) {
     assert(!widget.primary || debugCheckHasMediaQuery(context));
     assert(debugCheckHasMaterialLocalizations(context));
-    final ThemeData themeData = Theme.of(context);
+    final ThemeData theme = Theme.of(context);
     final AppBarTheme appBarTheme = AppBarTheme.of(context);
     final ScaffoldState scaffold = Scaffold.of(context, nullOk: true);
     final ModalRoute<dynamic> parentRoute = ModalRoute.of(context);
@@ -429,16 +429,16 @@
 
     IconThemeData overallIconTheme = widget.iconTheme
       ?? appBarTheme.iconTheme
-      ?? themeData.primaryIconTheme;
+      ?? theme.primaryIconTheme;
     IconThemeData actionsIconTheme = widget.actionsIconTheme
       ?? appBarTheme.actionsIconTheme
       ?? overallIconTheme;
     TextStyle centerStyle = widget.textTheme?.title
       ?? appBarTheme.textTheme?.title
-      ?? themeData.primaryTextTheme.title;
+      ?? theme.primaryTextTheme.title;
     TextStyle sideStyle = widget.textTheme?.body1
       ?? appBarTheme.textTheme?.body1
-      ?? themeData.primaryTextTheme.body1;
+      ?? theme.primaryTextTheme.body1;
 
     if (widget.toolbarOpacity != 1.0) {
       final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity);
@@ -477,7 +477,7 @@
     Widget title = widget.title;
     if (title != null) {
       bool namesRoute;
-      switch (defaultTargetPlatform) {
+      switch (theme.platform) {
         case TargetPlatform.android:
         case TargetPlatform.fuchsia:
            namesRoute = true;
@@ -524,7 +524,7 @@
       leading: leading,
       middle: title,
       trailing: actions,
-      centerMiddle: widget._getEffectiveCenterTitle(themeData),
+      centerMiddle: widget._getEffectiveCenterTitle(theme),
       middleSpacing: widget.titleSpacing,
     );
 
@@ -584,7 +584,7 @@
     }
     final Brightness brightness = widget.brightness
       ?? appBarTheme.brightness
-      ?? themeData.primaryColorBrightness;
+      ?? theme.primaryColorBrightness;
     final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark
       ? SystemUiOverlayStyle.light
       : SystemUiOverlayStyle.dark;
@@ -596,7 +596,7 @@
         child: Material(
           color: widget.backgroundColor
             ?? appBarTheme.color
-            ?? themeData.primaryColor,
+            ?? theme.primaryColor,
           elevation: widget.elevation
             ?? appBarTheme.elevation
             ?? _defaultElevation,
diff --git a/packages/flutter/lib/src/material/bottom_sheet.dart b/packages/flutter/lib/src/material/bottom_sheet.dart
index 8dbf54b..d947052 100644
--- a/packages/flutter/lib/src/material/bottom_sheet.dart
+++ b/packages/flutter/lib/src/material/bottom_sheet.dart
@@ -263,7 +263,7 @@
 
 class _ModalBottomSheetState<T> extends State<_ModalBottomSheet<T>> {
   String _getRouteLabel(MaterialLocalizations localizations) {
-    switch (defaultTargetPlatform) {
+    switch (Theme.of(context).platform) {
       case TargetPlatform.iOS:
         return '';
       case TargetPlatform.android:
diff --git a/packages/flutter/lib/src/material/dialog.dart b/packages/flutter/lib/src/material/dialog.dart
index 167c0ab..f23ddf7 100644
--- a/packages/flutter/lib/src/material/dialog.dart
+++ b/packages/flutter/lib/src/material/dialog.dart
@@ -319,7 +319,7 @@
         ),
       ));
     } else {
-      switch (defaultTargetPlatform) {
+      switch (theme.platform) {
         case TargetPlatform.iOS:
           label = semanticLabel;
           break;
@@ -587,16 +587,18 @@
     final List<Widget> body = <Widget>[];
     String label = semanticLabel;
 
+    final ThemeData theme = Theme.of(context);
+
     if (title != null) {
       body.add(Padding(
         padding: titlePadding,
         child: DefaultTextStyle(
-          style: Theme.of(context).textTheme.title,
+          style: theme.textTheme.title,
           child: Semantics(namesRoute: true, child: title),
         ),
       ));
     } else {
-      switch (defaultTargetPlatform) {
+      switch (theme.platform) {
         case TargetPlatform.iOS:
           label = semanticLabel;
           break;
diff --git a/packages/flutter/lib/src/material/drawer.dart b/packages/flutter/lib/src/material/drawer.dart
index a55f1e1..6aab9c8 100644
--- a/packages/flutter/lib/src/material/drawer.dart
+++ b/packages/flutter/lib/src/material/drawer.dart
@@ -11,6 +11,7 @@
 import 'list_tile.dart';
 import 'material.dart';
 import 'material_localizations.dart';
+import 'theme.dart';
 
 /// The possible alignments of a [Drawer].
 enum DrawerAlignment {
@@ -125,7 +126,7 @@
   Widget build(BuildContext context) {
     assert(debugCheckHasMaterialLocalizations(context));
     String label = semanticLabel;
-    switch (defaultTargetPlatform) {
+    switch (Theme.of(context).platform) {
       case TargetPlatform.iOS:
         label = semanticLabel;
         break;
@@ -469,6 +470,17 @@
         ),
       );
     } else {
+      bool platformHasBackButton;
+      switch (Theme.of(context).platform) {
+        case TargetPlatform.android:
+          platformHasBackButton = true;
+          break;
+        case TargetPlatform.iOS:
+        case TargetPlatform.fuchsia:
+          platformHasBackButton = false;
+          break;
+      }
+      assert(platformHasBackButton != null);
       return GestureDetector(
         key: _gestureDetectorKey,
         onHorizontalDragDown: _handleDragDown,
@@ -483,7 +495,7 @@
               BlockSemantics(
                 child: GestureDetector(
                   // On Android, the back button is used to dismiss a modal.
-                  excludeFromSemantics: defaultTargetPlatform == TargetPlatform.android,
+                  excludeFromSemantics: platformHasBackButton,
                   onTap: close,
                   child: Semantics(
                     label: MaterialLocalizations.of(context)?.modalBarrierDismissLabel,
diff --git a/packages/flutter/lib/src/material/flexible_space_bar.dart b/packages/flutter/lib/src/material/flexible_space_bar.dart
index 6c3ac7a..7e7c9ef 100644
--- a/packages/flutter/lib/src/material/flexible_space_bar.dart
+++ b/packages/flutter/lib/src/material/flexible_space_bar.dart
@@ -199,8 +199,10 @@
     }
 
     if (widget.title != null) {
+      final ThemeData theme = Theme.of(context);
+
       Widget title;
-      switch (defaultTargetPlatform) {
+      switch (theme.platform) {
         case TargetPlatform.iOS:
           title = widget.title;
           break;
@@ -212,7 +214,6 @@
           );
       }
 
-      final ThemeData theme = Theme.of(context);
       final double opacity = settings.toolbarOpacity;
       if (opacity > 0.0) {
         TextStyle titleStyle = theme.primaryTextTheme.title;
diff --git a/packages/flutter/lib/src/material/popup_menu.dart b/packages/flutter/lib/src/material/popup_menu.dart
index f0e1215..d9053fc 100644
--- a/packages/flutter/lib/src/material/popup_menu.dart
+++ b/packages/flutter/lib/src/material/popup_menu.dart
@@ -748,8 +748,9 @@
   assert(position != null);
   assert(items != null && items.isNotEmpty);
   assert(debugCheckHasMaterialLocalizations(context));
+
   String label = semanticLabel;
-  switch (defaultTargetPlatform) {
+  switch (Theme.of(context).platform) {
     case TargetPlatform.iOS:
       label = semanticLabel;
       break;
diff --git a/packages/flutter/lib/src/material/scrollbar.dart b/packages/flutter/lib/src/material/scrollbar.dart
index cfa1aee..50ee97c 100644
--- a/packages/flutter/lib/src/material/scrollbar.dart
+++ b/packages/flutter/lib/src/material/scrollbar.dart
@@ -53,10 +53,9 @@
 
 class _ScrollbarState extends State<Scrollbar> with TickerProviderStateMixin {
   ScrollbarPainter _materialPainter;
-  TargetPlatform _currentPlatform;
   TextDirection _textDirection;
   Color _themeColor;
-
+  bool _useCupertinoScrollbar;
   AnimationController _fadeoutAnimationController;
   Animation<double> _fadeoutOpacityAnimation;
   Timer _fadeoutTimer;
@@ -77,35 +76,36 @@
   @override
   void didChangeDependencies() {
     super.didChangeDependencies();
-
+    assert((() { _useCupertinoScrollbar = null; return true; })());
     final ThemeData theme = Theme.of(context);
-    _currentPlatform = theme.platform;
-
-    switch (_currentPlatform) {
+    switch (theme.platform) {
       case TargetPlatform.iOS:
         // On iOS, stop all local animations. CupertinoScrollbar has its own
         // animations.
         _fadeoutTimer?.cancel();
         _fadeoutTimer = null;
         _fadeoutAnimationController.reset();
+        _useCupertinoScrollbar = true;
         break;
       case TargetPlatform.android:
       case TargetPlatform.fuchsia:
         _themeColor = theme.highlightColor.withOpacity(1.0);
         _textDirection = Directionality.of(context);
         _materialPainter = _buildMaterialScrollbarPainter();
+        _useCupertinoScrollbar = false;
         break;
     }
+    assert(_useCupertinoScrollbar != null);
   }
 
   ScrollbarPainter _buildMaterialScrollbarPainter() {
     return ScrollbarPainter(
-        color: _themeColor,
-        textDirection: _textDirection,
-        thickness: _kScrollbarThickness,
-        fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
-        padding: MediaQuery.of(context).padding,
-      );
+      color: _themeColor,
+      textDirection: _textDirection,
+      thickness: _kScrollbarThickness,
+      fadeoutOpacityAnimation: _fadeoutOpacityAnimation,
+      padding: MediaQuery.of(context).padding,
+    );
   }
 
   bool _handleScrollNotification(ScrollNotification notification) {
@@ -116,9 +116,8 @@
 
     // iOS sub-delegates to the CupertinoScrollbar instead and doesn't handle
     // scroll notifications here.
-    if (_currentPlatform != TargetPlatform.iOS
-        && (notification is ScrollUpdateNotification
-            || notification is OverscrollNotification)) {
+    if (!_useCupertinoScrollbar &&
+        (notification is ScrollUpdateNotification || notification is OverscrollNotification)) {
       if (_fadeoutAnimationController.status != AnimationStatus.forward) {
         _fadeoutAnimationController.forward();
       }
@@ -143,25 +142,21 @@
 
   @override
   Widget build(BuildContext context) {
-    switch (_currentPlatform) {
-      case TargetPlatform.iOS:
-        return CupertinoScrollbar(
-          child: widget.child,
-        );
-      case TargetPlatform.android:
-      case TargetPlatform.fuchsia:
-        return NotificationListener<ScrollNotification>(
-          onNotification: _handleScrollNotification,
-          child: RepaintBoundary(
-            child: CustomPaint(
-              foregroundPainter: _materialPainter,
-              child: RepaintBoundary(
-                child: widget.child,
-              ),
-            ),
-          ),
-        );
+    if (_useCupertinoScrollbar) {
+      return CupertinoScrollbar(
+        child: widget.child,
+      );
     }
-    throw FlutterError('Unknown platform for scrollbar insertion');
+    return NotificationListener<ScrollNotification>(
+      onNotification: _handleScrollNotification,
+      child: RepaintBoundary(
+        child: CustomPaint(
+          foregroundPainter: _materialPainter,
+          child: RepaintBoundary(
+            child: widget.child,
+          ),
+        ),
+      ),
+    );
   }
 }
diff --git a/packages/flutter/lib/src/material/search.dart b/packages/flutter/lib/src/material/search.dart
index 016cf60..1fa2bfc 100644
--- a/packages/flutter/lib/src/material/search.dart
+++ b/packages/flutter/lib/src/material/search.dart
@@ -485,7 +485,7 @@
         break;
     }
     String routeName;
-    switch (defaultTargetPlatform) {
+    switch (theme.platform) {
       case TargetPlatform.iOS:
         routeName = '';
         break;
diff --git a/packages/flutter/lib/src/material/theme_data.dart b/packages/flutter/lib/src/material/theme_data.dart
index dacad32..820b2d4 100644
--- a/packages/flutter/lib/src/material/theme_data.dart
+++ b/packages/flutter/lib/src/material/theme_data.dart
@@ -774,14 +774,26 @@
 
   /// The platform the material widgets should adapt to target.
   ///
-  /// Defaults to the current platform. This should be used in order to style UI
-  /// elements according to platform conventions.
+  /// Defaults to the current platform, as exposed by [defaultTargetPlatform].
+  /// This should be used in order to style UI elements according to platform
+  /// conventions.
   ///
-  /// [Platform.defaultTargetPlatform] should be used directly instead only in
-  /// rare cases where it's necessary to determine behavior based on the
-  /// platform. [dart.io.Platform.environment] should be used when it's critical
+  /// Widgets from the material library should use this getter (via [Theme.of])
+  /// to determine the current platform for the purpose of emulating the
+  /// platform behavior (e.g. scrolling or haptic effects). Widgets and render
+  /// objects at lower layers that try to emulate the underlying platform
+  /// platform can depend on [defaultTargetPlatform] directly, or may require
+  /// that the target platform be provided as an argument. The
+  /// [dart.io.Platform] object should only be used directly when it's critical
   /// to actually know the current platform, without any overrides possible (for
   /// example, when a system API is about to be called).
+  ///
+  /// In a test environment, the platform returned is [TargetPlatform.android]
+  /// regardless of the host platform. (Android was chosen because the tests
+  /// were originally written assuming Android-like behavior, and we added
+  /// platform adaptations for iOS later). Tests can check iOS behavior by
+  /// setting the [platform] of the [Theme] explicitly to [TargetPlatform.iOS],
+  /// or by setting [debugDefaultTargetPlatformOverride].
   final TargetPlatform platform;
 
   /// Configures the hit test size of certain Material widgets.
diff --git a/packages/flutter/lib/src/material/time_picker.dart b/packages/flutter/lib/src/material/time_picker.dart
index d5cf674..1745cdf 100644
--- a/packages/flutter/lib/src/material/time_picker.dart
+++ b/packages/flutter/lib/src/material/time_picker.dart
@@ -203,8 +203,13 @@
     if (fragmentContext.selectedTime.period == DayPeriod.am) {
       return;
     }
-    if (fragmentContext.targetPlatform == TargetPlatform.android) {
-      _announceToAccessibility(context, MaterialLocalizations.of(context).anteMeridiemAbbreviation);
+    switch (fragmentContext.targetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        _announceToAccessibility(context, MaterialLocalizations.of(context).anteMeridiemAbbreviation);
+        break;
+      case TargetPlatform.iOS:
+        break;
     }
     _togglePeriod();
   }
@@ -213,8 +218,13 @@
     if (fragmentContext.selectedTime.period == DayPeriod.pm) {
       return;
     }
-    if (fragmentContext.targetPlatform == TargetPlatform.android) {
-      _announceToAccessibility(context, MaterialLocalizations.of(context).postMeridiemAbbreviation);
+    switch (fragmentContext.targetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        _announceToAccessibility(context, MaterialLocalizations.of(context).postMeridiemAbbreviation);
+        break;
+      case TargetPlatform.iOS:
+        break;
     }
     _togglePeriod();
   }
diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart
index f770544..67fefbd 100644
--- a/packages/flutter/lib/src/rendering/editable.dart
+++ b/packages/flutter/lib/src/rendering/editable.dart
@@ -391,13 +391,8 @@
   // TODO(goderbauer): doesn't handle extended grapheme clusters with more than one Unicode scalar value (https://github.com/flutter/flutter/issues/13404).
   void _handleKeyEvent(RawKeyEvent keyEvent) {
     // Only handle key events on Android.
-    switch (defaultTargetPlatform) {
-      case TargetPlatform.android:
-        break;
-      case TargetPlatform.iOS:
-      case TargetPlatform.fuchsia:
-        return;
-    }
+    if (keyEvent.data is! RawKeyEventDataAndroid)
+      return;
 
     if (keyEvent is RawKeyUpEvent)
       return;
@@ -1594,12 +1589,15 @@
   /// of the cursor for iOS is approximate and obtained through an eyeball
   /// comparison.
   Rect get _getCaretPrototype {
-    switch(defaultTargetPlatform){
+    assert(defaultTargetPlatform != null);
+    switch (defaultTargetPlatform) {
       case TargetPlatform.iOS:
         return Rect.fromLTWH(0.0, 0.0, cursorWidth, preferredLineHeight + 2);
-      default:
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
         return Rect.fromLTWH(0.0, _kCaretHeightOffset, cursorWidth, preferredLineHeight - 2.0 * _kCaretHeightOffset);
     }
+    return null;
   }
   @override
   void performLayout() {
@@ -1647,10 +1645,11 @@
     if (_cursorOffset != null)
       caretRect = caretRect.shift(_cursorOffset);
 
-    if (_textPainter.getFullHeightForCaret(textPosition, _caretPrototype) != null) {
+    final double caretHeight = _textPainter.getFullHeightForCaret(textPosition, _caretPrototype);
+    if (caretHeight != null) {
       switch (defaultTargetPlatform) {
-        case TargetPlatform.iOS: {
-          final double heightDiff = _textPainter.getFullHeightForCaret(textPosition, _caretPrototype) - caretRect.height;
+        case TargetPlatform.iOS:
+          final double heightDiff = caretHeight - caretRect.height;
           // Center the caret vertically along the text.
           caretRect = Rect.fromLTWH(
             caretRect.left,
@@ -1659,8 +1658,8 @@
             caretRect.height,
           );
           break;
-        }
-        default: {
+        case TargetPlatform.android:
+        case TargetPlatform.fuchsia:
           // Override the height to take the full height of the glyph at the TextPosition
           // when not on iOS. iOS has special handling that creates a taller caret.
           // TODO(garyq): See the TODO for _getCaretPrototype.
@@ -1668,10 +1667,9 @@
             caretRect.left,
             caretRect.top - _kCaretHeightOffset,
             caretRect.width,
-            _textPainter.getFullHeightForCaret(textPosition, _caretPrototype),
+            caretHeight,
           );
           break;
-        }
       }
     }
 
diff --git a/packages/flutter/lib/src/widgets/modal_barrier.dart b/packages/flutter/lib/src/widgets/modal_barrier.dart
index 37b66e5..d6936b0 100644
--- a/packages/flutter/lib/src/widgets/modal_barrier.dart
+++ b/packages/flutter/lib/src/widgets/modal_barrier.dart
@@ -75,7 +75,18 @@
   @override
   Widget build(BuildContext context) {
     assert(!dismissible || semanticsLabel == null || debugCheckHasDirectionality(context));
-    final bool semanticsDismissible = dismissible && defaultTargetPlatform != TargetPlatform.android;
+    bool platformSupportsDismissingBarrier;
+    switch (defaultTargetPlatform) {
+      case TargetPlatform.android:
+      case TargetPlatform.fuchsia:
+        platformSupportsDismissingBarrier = false;
+        break;
+      case TargetPlatform.iOS:
+        platformSupportsDismissingBarrier = true;
+        break;
+    }
+    assert(platformSupportsDismissingBarrier != null);
+    final bool semanticsDismissible = dismissible && platformSupportsDismissingBarrier;
     final bool modalBarrierSemanticsDismissible = barrierSemanticsDismissible ?? semanticsDismissible;
     return BlockSemantics(
       child: ExcludeSemantics(