Override MediaQuery for WidgetsApp (#81295)

diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart
index 7947faa..a4489ef 100644
--- a/packages/flutter/lib/src/widgets/app.dart
+++ b/packages/flutter/lib/src/widgets/app.dart
@@ -236,6 +236,9 @@
 /// It is used by both [MaterialApp] and [CupertinoApp] to implement base
 /// functionality for an app.
 ///
+/// If a [MediaQuery] is not available above [WidgetsApp], a [MediaQuery] is
+/// built using [MediaQuery.fromWindow].
+///
 /// Find references to many of the widgets that [WidgetsApp] wraps in the "See
 /// also" section.
 ///
@@ -247,6 +250,8 @@
 ///    without an explicit style.
 ///  * [MediaQuery], which establishes a subtree in which media queries resolve
 ///    to a [MediaQueryData].
+///  * [MediaQuery.fromWindow], which builds a [MediaQuery] with data derived
+///    from [WidgetsBinding.window].
 ///  * [Localizations], which defines the [Locale] for its `child`.
 ///  * [Title], a widget that describes this app in the operating system.
 ///  * [Navigator], a widget that manages a set of child widgets with a stack
@@ -1635,6 +1640,19 @@
 
     assert(_debugCheckLocalizations(appLocale));
 
+    Widget child = Localizations(
+      locale: appLocale,
+      delegates: _localizationsDelegates.toList(),
+      child: title,
+    );
+
+    final MediaQueryData? data = MediaQuery.maybeOf(context);
+    if (data == null) {
+      child = MediaQuery.fromWindow(
+        child: child,
+      );
+    }
+
     return RootRestorationScope(
       restorationId: widget.restorationScopeId,
       child: Shortcuts(
@@ -1648,13 +1666,7 @@
             child: DefaultTextEditingActions(
               child: FocusTraversalGroup(
                 policy: ReadingOrderTraversalPolicy(),
-                child: _MediaQueryFromWindow(
-                  child: Localizations(
-                    locale: appLocale,
-                    delegates: _localizationsDelegates.toList(),
-                    child: title,
-                  ),
-                ),
+                child: child,
               ),
             ),
           ),
@@ -1663,81 +1675,3 @@
     );
   }
 }
-
-/// Builds [MediaQuery] from `window` by listening to [WidgetsBinding].
-///
-/// It is performed in a standalone widget to rebuild **only** [MediaQuery] and
-/// its dependents when `window` changes, instead of rebuilding the entire widget tree.
-class _MediaQueryFromWindow extends StatefulWidget {
-  const _MediaQueryFromWindow({Key? key, required this.child}) : super(key: key);
-
-  final Widget child;
-
-  @override
-  _MediaQueryFromWindowsState createState() => _MediaQueryFromWindowsState();
-}
-
-class _MediaQueryFromWindowsState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
-  @override
-  void initState() {
-    super.initState();
-    WidgetsBinding.instance!.addObserver(this);
-  }
-
-  // ACCESSIBILITY
-
-  @override
-  void didChangeAccessibilityFeatures() {
-    setState(() {
-      // The properties of window have changed. We use them in our build
-      // function, so we need setState(), but we don't cache anything locally.
-    });
-  }
-
-  // METRICS
-
-  @override
-  void didChangeMetrics() {
-    setState(() {
-      // The properties of window have changed. We use them in our build
-      // function, so we need setState(), but we don't cache anything locally.
-    });
-  }
-
-  @override
-  void didChangeTextScaleFactor() {
-    setState(() {
-      // The textScaleFactor property of window has changed. We reference
-      // window in our build function, so we need to call setState(), but
-      // we don't need to cache anything locally.
-    });
-  }
-
-  // RENDERING
-  @override
-  void didChangePlatformBrightness() {
-    setState(() {
-      // The platformBrightness property of window has changed. We reference
-      // window in our build function, so we need to call setState(), but
-      // we don't need to cache anything locally.
-    });
-  }
-
-  @override
-  Widget build(BuildContext context) {
-    MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
-    if (!kReleaseMode) {
-      data = data.copyWith(platformBrightness: debugBrightnessOverride);
-    }
-    return MediaQuery(
-      data: data,
-      child: widget.child,
-    );
-  }
-
-  @override
-  void dispose() {
-    WidgetsBinding.instance!.removeObserver(this);
-    super.dispose();
-  }
-}
diff --git a/packages/flutter/lib/src/widgets/media_query.dart b/packages/flutter/lib/src/widgets/media_query.dart
index ffbdf8f..bcbf903 100644
--- a/packages/flutter/lib/src/widgets/media_query.dart
+++ b/packages/flutter/lib/src/widgets/media_query.dart
@@ -9,6 +9,7 @@
 import 'package:flutter/foundation.dart';
 
 import 'basic.dart';
+import 'binding.dart';
 import 'debug.dart';
 import 'framework.dart';
 
@@ -787,6 +788,32 @@
     );
   }
 
+  /// Creates a [_MediaQueryFromWindow] which builds and updates a
+  /// [MediaQuery] using the latest [WidgetsBinding.window] values.
+  ///
+  /// The [MediaQuery] is wrapped in a separate widget to ensure that only it
+  /// and its dependents are updated when `window` changes, instead of
+  /// rebuilding the whole widget tree.
+  ///
+  /// This should be inserted into the widget tree when the [MediaQuery] view
+  /// padding is consumed by a widget in such a way that the view padding is no
+  /// longer exposed to the widget's descendants or siblings.
+  ///
+  /// The [child] argument is required and must not be null.
+  ///
+  /// See also:
+  ///
+  ///  * [_MediaQueryFromWindow], the underlying widget.
+  static Widget fromWindow({
+    Key? key,
+    required Widget child,
+  }) {
+    return _MediaQueryFromWindow(
+      key: key,
+      child: child,
+    );
+  }
+
   /// Contains information about the current media.
   ///
   /// For example, the [MediaQueryData.size] property contains the width and
@@ -922,3 +949,100 @@
   /// focus (although they remain disabled) when traversed.
   directional,
 }
+
+/// Provides a [MediaQuery] which is built and updated using the latest
+/// [WidgetsBinding.window] values.
+///
+/// Receives `window` updates by listening to [WidgetsBinding].
+///
+/// The standalone widget ensures that it rebuilds **only** [MediaQuery] and
+/// its dependents when `window` changes, instead of rebuilding the entire
+/// widget tree.
+///
+/// It is used by [WidgetsApp] if no other [MediaQuery] is available above it.
+///
+/// See also:
+///
+///  * [MediaQuery], which establishes a subtree in which media queries resolve
+///    to a [MediaQueryData].
+class _MediaQueryFromWindow extends StatefulWidget {
+  /// Creates a [_MediaQueryFromWindow] that provides a [MediaQuery] to its
+  /// descendants using the `window` to keep [MediaQueryData] up to date.
+  ///
+  /// The [child] must not be null.
+  const _MediaQueryFromWindow({
+    Key? key,
+    required this.child,
+  }) : super(key: key);
+
+  /// {@macro flutter.widgets.ProxyWidget.child}
+  final Widget child;
+
+  @override
+  State<_MediaQueryFromWindow> createState() => _MediaQueryFromWindowState();
+}
+
+class _MediaQueryFromWindowState extends State<_MediaQueryFromWindow> with WidgetsBindingObserver {
+  @override
+  void initState() {
+    super.initState();
+    WidgetsBinding.instance!.addObserver(this);
+  }
+
+  // ACCESSIBILITY
+
+  @override
+  void didChangeAccessibilityFeatures() {
+    setState(() {
+      // The properties of window have changed. We use them in our build
+      // function, so we need setState(), but we don't cache anything locally.
+    });
+  }
+
+  // METRICS
+
+  @override
+  void didChangeMetrics() {
+    setState(() {
+      // The properties of window have changed. We use them in our build
+      // function, so we need setState(), but we don't cache anything locally.
+    });
+  }
+
+  @override
+  void didChangeTextScaleFactor() {
+    setState(() {
+      // The textScaleFactor property of window has changed. We reference
+      // window in our build function, so we need to call setState(), but
+      // we don't need to cache anything locally.
+    });
+  }
+
+  // RENDERING
+  @override
+  void didChangePlatformBrightness() {
+    setState(() {
+      // The platformBrightness property of window has changed. We reference
+      // window in our build function, so we need to call setState(), but
+      // we don't need to cache anything locally.
+    });
+  }
+
+  @override
+  Widget build(BuildContext context) {
+    MediaQueryData data = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
+    if (!kReleaseMode) {
+      data = data.copyWith(platformBrightness: debugBrightnessOverride);
+    }
+    return MediaQuery(
+      data: data,
+      child: widget.child,
+    );
+  }
+
+  @override
+  void dispose() {
+    WidgetsBinding.instance!.removeObserver(this);
+    super.dispose();
+  }
+}
diff --git a/packages/flutter/test/widgets/app_test.dart b/packages/flutter/test/widgets/app_test.dart
index d0c6778..c423d44 100644
--- a/packages/flutter/test/widgets/app_test.dart
+++ b/packages/flutter/test/widgets/app_test.dart
@@ -455,6 +455,39 @@
       const Locale('zh'),
     );
   });
+
+  testWidgets('WidgetsApp creates a MediaQuery', (WidgetTester tester) async {
+    late BuildContext capturedContext;
+    await tester.pumpWidget(
+      WidgetsApp(
+        builder: (BuildContext context, Widget? child) {
+          capturedContext = context;
+          return const Placeholder();
+        },
+        color: const Color(0xFF123456),
+      ),
+    );
+    expect(MediaQuery.of(capturedContext), isNotNull);
+  });
+
+  testWidgets('WidgetsApp does not create MediaQuery if one is already available', (WidgetTester tester) async {
+    late BuildContext capturedContext;
+    final UniqueKey uniqueKey = UniqueKey();
+    await tester.pumpWidget(
+      MediaQuery(
+      key: uniqueKey,
+        data: const MediaQueryData(),
+        child: WidgetsApp(
+          builder: (BuildContext context, Widget? child) {
+            capturedContext = context;
+            return const Placeholder();
+          },
+          color: const Color(0xFF123456),
+        ),
+      ),
+    );
+    expect(capturedContext.dependOnInheritedWidgetOfExactType<MediaQuery>()?.key, uniqueKey);
+  });
 }
 
 typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
diff --git a/packages/flutter/test/widgets/media_query_test.dart b/packages/flutter/test/widgets/media_query_test.dart
index 7b71949..d5608c1 100644
--- a/packages/flutter/test/widgets/media_query_test.dart
+++ b/packages/flutter/test/widgets/media_query_test.dart
@@ -627,4 +627,49 @@
     expect(outsideBoldTextOverride, false);
     expect(insideBoldTextOverride, true);
   });
+
+  testWidgets('MediaQuery.fromWindow creates a MediaQuery', (WidgetTester tester) async {
+    bool hasMediaQueryAsParentOutside = false;
+    bool hasMediaQueryAsParentInside = false;
+
+    await tester.pumpWidget(
+      Builder(
+        builder: (BuildContext context) {
+          hasMediaQueryAsParentOutside =
+              context.findAncestorWidgetOfExactType<MediaQuery>() != null;
+          return MediaQuery.fromWindow(
+            child: Builder(
+              builder: (BuildContext context) {
+                hasMediaQueryAsParentInside =
+                    context.findAncestorWidgetOfExactType<MediaQuery>() != null;
+                return const SizedBox();
+              },
+            ),
+          );
+        },
+      ),
+    );
+
+    expect(hasMediaQueryAsParentOutside, false);
+    expect(hasMediaQueryAsParentInside, true);
+  });
+
+  testWidgets('MediaQueryData.fromWindow is created using window values', (WidgetTester tester)
+  async {
+    final MediaQueryData windowData = MediaQueryData.fromWindow(WidgetsBinding.instance!.window);
+    late MediaQueryData fromWindowData;
+
+    await tester.pumpWidget(
+      MediaQuery.fromWindow(
+        child: Builder(
+          builder: (BuildContext context) {
+            fromWindowData = MediaQuery.of(context);
+            return const SizedBox();
+          },
+        ),
+      ),
+    );
+
+    expect(windowData, equals(fromWindowData));
+  });
 }