Add supported OS version tables to READMEs (#5106)

diff --git a/.cirrus.yml b/.cirrus.yml
index 8f6a6f7..66e8336 100644
--- a/.cirrus.yml
+++ b/.cirrus.yml
@@ -101,6 +101,7 @@
       always:
         format_script: ./script/tool_runner.sh format --fail-on-change
         pubspec_script: ./script/tool_runner.sh pubspec-check
+        readme_script: ./script/tool_runner.sh readme-check
         license_script: dart $PLUGIN_TOOL license-check
     - name: federated_safety
       # This check is only meaningful for PRs, as it validates changes
diff --git a/packages/espresso/CHANGELOG.md b/packages/espresso/CHANGELOG.md
index a141b23..b76cf2a 100644
--- a/packages/espresso/CHANGELOG.md
+++ b/packages/espresso/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 0.2.0
 
 * Updates compileSdkVersion to 31.
diff --git a/packages/espresso/README.md b/packages/espresso/README.md
index 7747560..68c3b55 100644
--- a/packages/espresso/README.md
+++ b/packages/espresso/README.md
@@ -2,6 +2,10 @@
 
 Provides bindings for Espresso tests of Flutter Android apps.
 
+|             | Android |
+|-------------|---------|
+| **Support** | SDK 16+ |
+
 ## Installation
 
 Add the `espresso` package as a `dev_dependency` in your app's pubspec.yaml. If you're testing the example app of a package, add it as a dev_dependency of the main package as well.
@@ -99,4 +103,3 @@
   --results-bucket=<RESULTS_BUCKET> \
   --results-dir=<RESULTS_DIRECTORY>
 ```
-
diff --git a/packages/file_selector/file_selector/CHANGELOG.md b/packages/file_selector/file_selector/CHANGELOG.md
index 65b3a64..7eeedad 100644
--- a/packages/file_selector/file_selector/CHANGELOG.md
+++ b/packages/file_selector/file_selector/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 0.8.4+1
 
 * Adds README information about macOS entitlements.
diff --git a/packages/file_selector/file_selector/README.md b/packages/file_selector/file_selector/README.md
index 863164e..544cde8 100644
--- a/packages/file_selector/file_selector/README.md
+++ b/packages/file_selector/file_selector/README.md
@@ -4,6 +4,10 @@
 
 A Flutter plugin that manages files and interactions with file dialogs.
 
+|             | macOS  | Web | Windows     |
+|-------------|--------|-----|-------------|
+| **Support** | 10.11+ | Any | Windows 10+ |
+
 ## Usage
 To use this plugin, add `file_selector` as a [dependency in your pubspec.yaml file](https://flutter.dev/platform-plugins/).
 
diff --git a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md
index 8b110b7..8fdfc39 100644
--- a/packages/flutter_plugin_android_lifecycle/CHANGELOG.md
+++ b/packages/flutter_plugin_android_lifecycle/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 2.0.5
 
 * Updates compileSdkVersion to 31.
diff --git a/packages/flutter_plugin_android_lifecycle/README.md b/packages/flutter_plugin_android_lifecycle/README.md
index 3290140..2475230 100644
--- a/packages/flutter_plugin_android_lifecycle/README.md
+++ b/packages/flutter_plugin_android_lifecycle/README.md
@@ -9,6 +9,10 @@
 Android embedding plugins API is to force plugins to have a pub constraint that signifies the
 major version of the Android `Lifecycle` API they expect.
 
+|             | Android |
+|-------------|---------|
+| **Support** | SDK 16+ |
+
 ## Installation
 
 Add `flutter_plugin_android_lifecycle` as a [dependency in your pubspec.yaml file](https://flutter.dev/using-packages/).
@@ -32,7 +36,7 @@
     Lifecycle lifecycle = FlutterLifecycleAdapter.getActivityLifecycle(binding);
     // Use lifecycle as desired.
   }
-  
+
   //...
 }
 ```
diff --git a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
index f05de47..3ecea7c 100644
--- a/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
+++ b/packages/google_maps_flutter/google_maps_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 2.1.3
 
 * Fixes iOS crash on `EXC_BAD_ACCESS KERN_PROTECTION_FAILURE` if the map frame changes long after creation.
diff --git a/packages/google_maps_flutter/google_maps_flutter/README.md b/packages/google_maps_flutter/google_maps_flutter/README.md
index 038126f..ae9a659 100644
--- a/packages/google_maps_flutter/google_maps_flutter/README.md
+++ b/packages/google_maps_flutter/google_maps_flutter/README.md
@@ -4,6 +4,10 @@
 
 A Flutter plugin that provides a [Google Maps](https://developers.google.com/maps/) widget.
 
+|             | Android | iOS    |
+|-------------|---------|--------|
+| **Support** | SDK 20+ | iOS 9+ |
+
 ## Usage
 
 To use this plugin, add `google_maps_flutter` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
@@ -60,7 +64,7 @@
 
 ### iOS
 
-This plugin requires iOS 9.0 or higher. To set up, specify your API key in the application delegate `ios/Runner/AppDelegate.m`:
+To set up, specify your API key in the application delegate `ios/Runner/AppDelegate.m`:
 
 ```objectivec
 #include "AppDelegate.h"
diff --git a/packages/google_sign_in/google_sign_in/CHANGELOG.md b/packages/google_sign_in/google_sign_in/CHANGELOG.md
index a46023b..c0a3776 100644
--- a/packages/google_sign_in/google_sign_in/CHANGELOG.md
+++ b/packages/google_sign_in/google_sign_in/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 5.2.4
 
 * Internal code cleanup for stricter analysis options.
diff --git a/packages/google_sign_in/google_sign_in/README.md b/packages/google_sign_in/google_sign_in/README.md
index f378747..2d6fa7c 100644
--- a/packages/google_sign_in/google_sign_in/README.md
+++ b/packages/google_sign_in/google_sign_in/README.md
@@ -6,6 +6,10 @@
 available yet. [Feedback](https://github.com/flutter/flutter/issues) and
 [Pull Requests](https://github.com/flutter/plugins/pulls) are most welcome!
 
+|             | Android | iOS    | Web |
+|-------------|---------|--------|-----|
+| **Support** | SDK 16+ | iOS 9+ | Any |
+
 ## Platform integration
 
 ### Android integration
diff --git a/packages/image_picker/image_picker/CHANGELOG.md b/packages/image_picker/image_picker/CHANGELOG.md
index 2ba5a2c..aa4a285 100644
--- a/packages/image_picker/image_picker/CHANGELOG.md
+++ b/packages/image_picker/image_picker/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 0.8.4+11
 
 * Fixes Activity leak.
diff --git a/packages/image_picker/image_picker/README.md b/packages/image_picker/image_picker/README.md
index 46a7795..2fa20be 100755
--- a/packages/image_picker/image_picker/README.md
+++ b/packages/image_picker/image_picker/README.md
@@ -5,6 +5,10 @@
 A Flutter plugin for iOS and Android for picking images from the image library,
 and taking new pictures with the camera.
 
+|             | Android | iOS    | Web                              |
+|-------------|---------|--------|----------------------------------|
+| **Support** | SDK 21+ | iOS 9+ | [See `image_picker_for_web `][1] |
+
 ## Installation
 
 First, add `image_picker` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
@@ -26,9 +30,9 @@
 
 Starting with version **0.8.1** the Android implementation support to pick (multiple) images on Android 4.3 or higher.
 
-No configuration required - the plugin should work out of the box. It is 
+No configuration required - the plugin should work out of the box. It is
 however highly recommended to prepare for Android killing the application when
-low on memory. How to prepare for this is discussed in the [Handling 
+low on memory. How to prepare for this is discussed in the [Handling
 MainActivity destruction on Android](#handling-mainactivity-destruction-on-android)
 section.
 
@@ -60,12 +64,12 @@
 ### Handling MainActivity destruction on Android
 
 When under high memory pressure the Android system may kill the MainActivity of
-the application using the image_picker. On Android the image_picker makes use 
-of the default `Intent.ACTION_GET_CONTENT` or `MediaStore.ACTION_IMAGE_CAPTURE` 
-intents. This means that while the intent is executing the source application 
+the application using the image_picker. On Android the image_picker makes use
+of the default `Intent.ACTION_GET_CONTENT` or `MediaStore.ACTION_IMAGE_CAPTURE`
+intents. This means that while the intent is executing the source application
 is moved to the background and becomes eligable for cleanup when the system is
-low on memory. When the intent finishes executing, Android will restart the 
-application. Since the data is never returned to the original call use the 
+low on memory. When the intent finishes executing, Android will restart the
+application. Since the data is never returned to the original call use the
 `ImagePicker.retrieveLostData()` method to retrieve the lost data. For example:
 
 ```dart
@@ -85,9 +89,9 @@
 }
 ```
 
-This check should always be run at startup in order to detect and handle this 
-case. Please refer to the 
-[example app](https://pub.dev/packages/image_picker/example) for a more 
+This check should always be run at startup in order to detect and handle this
+case. Please refer to the
+[example app](https://pub.dev/packages/image_picker/example) for a more
 complete example of handling this flow.
 
 ## Migrating to 0.8.2+
@@ -101,4 +105,6 @@
 | `PickedFile image = await _picker.getImage(...)` | `XFile image = await _picker.pickImage(...)` |
 | `List<PickedFile> images = await _picker.getMultiImage(...)` | `List<XFile> images = await _picker.pickMultiImage(...)` |
 | `PickedFile video = await _picker.getVideo(...)` | `XFile video = await _picker.pickVideo(...)` |
-| `LostData response = await _picker.getLostData()` | `LostDataResponse response = await _picker.retrieveLostData()` |
\ No newline at end of file
+| `LostData response = await _picker.getLostData()` | `LostDataResponse response = await _picker.retrieveLostData()` |
+
+[1]: https://pub.dev/packages/image_picker_for_web#limitations-on-the-web-platform
diff --git a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md
index 6253a23..cfe625a 100644
--- a/packages/in_app_purchase/in_app_purchase/CHANGELOG.md
+++ b/packages/in_app_purchase/in_app_purchase/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 3.0.2
 
 * Adds additional explanation on why it is important to complete a purchase.
diff --git a/packages/in_app_purchase/in_app_purchase/README.md b/packages/in_app_purchase/in_app_purchase/README.md
index dbc8e8a..258eba7 100644
--- a/packages/in_app_purchase/in_app_purchase/README.md
+++ b/packages/in_app_purchase/in_app_purchase/README.md
@@ -5,6 +5,10 @@
 This plugin supports in-app purchases (_IAP_) through an _underlying store_,
 which can be the App Store (on iOS) or Google Play (on Android).
 
+|             | Android | iOS  |
+|-------------|---------|------|
+| **Support** | SDK 16+ | 9.0+ |
+
 <p>
   <img src="https://github.com/flutter/plugins/blob/master/packages/in_app_purchase/in_app_purchase/doc/iap_ios.gif?raw=true"
     alt="An animated image of the iOS in-app purchase UI" height="400"/>
@@ -32,7 +36,7 @@
 * [App Store documentation](https://developer.apple.com/in-app-purchase/)
 * [Google Play documentation](https://developer.android.com/google/play/billing/billing_overview)
 
-> NOTE: Further in this document the App Store and Google Play will be referred 
+> NOTE: Further in this document the App Store and Google Play will be referred
 > to as "the store" or "the underlying store", except when a feature is specific
 > to a particular store.
 
@@ -195,12 +199,12 @@
 ### Completing a purchase
 
 The `InAppPurchase.purchaseStream` will send purchase updates after initiating
-the purchase flow using `InAppPurchase.buyConsumable` or 
-`InAppPurchase.buyNonConsumable`. After verifying the purchase receipt and the 
-delivering the content to the user it is important to call 
+the purchase flow using `InAppPurchase.buyConsumable` or
+`InAppPurchase.buyNonConsumable`. After verifying the purchase receipt and the
+delivering the content to the user it is important to call
 `InAppPurchase.completePurchase` to tell the underlying store that the
-purchase has been completed. Calling `InAppPurchase.completePurchase` will 
-inform the underlying store that the app verified and processed the 
+purchase has been completed. Calling `InAppPurchase.completePurchase` will
+inform the underlying store that the app verified and processed the
 purchase and the store can proceed to finalize the transaction and bill
 the end user's payment account.
 
diff --git a/packages/ios_platform_images/CHANGELOG.md b/packages/ios_platform_images/CHANGELOG.md
index 416f936..f2fb43d 100644
--- a/packages/ios_platform_images/CHANGELOG.md
+++ b/packages/ios_platform_images/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 0.2.0+4
 
 * Internal code cleanup for stricter analysis options.
diff --git a/packages/ios_platform_images/README.md b/packages/ios_platform_images/README.md
index ada89fc..08dfc3e 100644
--- a/packages/ios_platform_images/README.md
+++ b/packages/ios_platform_images/README.md
@@ -8,6 +8,10 @@
 When loading images from Image.xcassets the device specific variant is chosen
 ([iOS documentation](https://developer.apple.com/design/human-interface-guidelines/ios/icons-and-images/image-size-and-resolution/)).
 
+|             | iOS  |
+|-------------|------|
+| **Support** | 9.0+ |
+
 ## Usage
 
 ### iOS->Flutter Example
diff --git a/packages/local_auth/local_auth/CHANGELOG.md b/packages/local_auth/local_auth/CHANGELOG.md
index 9387540..a2f93ca 100644
--- a/packages/local_auth/local_auth/CHANGELOG.md
+++ b/packages/local_auth/local_auth/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 1.1.11
 
 * Adds support `localizedFallbackTitle` in authenticateWithBiometrics on iOS.
diff --git a/packages/local_auth/local_auth/README.md b/packages/local_auth/local_auth/README.md
index 84470c6..3a60e45 100644
--- a/packages/local_auth/local_auth/README.md
+++ b/packages/local_auth/local_auth/README.md
@@ -6,7 +6,11 @@
 This means referring to biometric authentication on iOS (Touch ID or lock code)
 and the fingerprint APIs on Android (introduced in Android 6.0).
 
-## Usage in Dart
+|             | Android   | iOS  |
+|-------------|-----------|------|
+| **Support** | SDK 16+\* | 9.0+ |
+
+## Usage
 
 Import the relevant file:
 
@@ -134,6 +138,11 @@
 }
 ```
 
+### Android
+
+\* The plugin will build and run on SDK 16+, but `isDeviceSupported()` will
+always return false before SDK 23 (Android 6.0).
+
 ## iOS Integration
 
 Note that this plugin works with both Touch ID and Face ID. However, to use the latter,
diff --git a/packages/path_provider/path_provider/CHANGELOG.md b/packages/path_provider/path_provider/CHANGELOG.md
index d71ebf7..4714ad9 100644
--- a/packages/path_provider/path_provider/CHANGELOG.md
+++ b/packages/path_provider/path_provider/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 2.0.9
 
 * Updates documentation on README.md.
diff --git a/packages/path_provider/path_provider/README.md b/packages/path_provider/path_provider/README.md
index 20d888f..e79234f 100644
--- a/packages/path_provider/path_provider/README.md
+++ b/packages/path_provider/path_provider/README.md
@@ -2,10 +2,14 @@
 
 [![pub package](https://img.shields.io/pub/v/path_provider.svg)](https://pub.dev/packages/path_provider)
 
-A Flutter plugin for finding commonly used locations on the filesystem. 
+A Flutter plugin for finding commonly used locations on the filesystem.
 Supports Android, iOS, Linux, macOS and Windows.
 Not all methods are supported on all platforms.
 
+|             | Android | iOS  | Linux | macOS  | Windows     |
+|-------------|---------|------|-------|--------|-------------|
+| **Support** | SDK 16+ | 9.0+ | Any   | 10.11+ | Windows 10+ |
+
 ## Usage
 
 To use this plugin, add `path_provider` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
@@ -40,4 +44,3 @@
 With that change, tests should be updated to mock `PathProviderPlatform` rather than `PlatformChannel`.
 
 See this `path_provider` [test](https://github.com/flutter/plugins/blob/master/packages/path_provider/path_provider/test/path_provider_test.dart) for an example.
-
diff --git a/packages/quick_actions/quick_actions/CHANGELOG.md b/packages/quick_actions/quick_actions/CHANGELOG.md
index e95d56c..2a764a5 100644
--- a/packages/quick_actions/quick_actions/CHANGELOG.md
+++ b/packages/quick_actions/quick_actions/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## NEXT
 
 * Updates minimum Flutter version to 2.8.
+* Adds OS version support information to README.
 
 ## 0.6.0+10
 
diff --git a/packages/quick_actions/quick_actions/README.md b/packages/quick_actions/quick_actions/README.md
index 46e87fa..10bae16 100644
--- a/packages/quick_actions/quick_actions/README.md
+++ b/packages/quick_actions/quick_actions/README.md
@@ -7,10 +7,13 @@
 concept](https://developer.apple.com/design/human-interface-guidelines/ios/system-capabilities/home-screen-actions/)
 on iOS and to the [App
 Shortcuts](https://developer.android.com/guide/topics/ui/shortcuts.html) APIs on
-Android (introduced in Android 7.1 / API level 25). It is safe to run this plugin
-with earlier versions of Android as it will produce a noop.
+Android.
 
-## Usage in Dart
+|             | Android   | iOS  |
+|-------------|-----------|------|
+| **Support** | SDK 16+\* | 9.0+ |
+
+## Usage
 
 Initialize the library early in your application's lifecycle by providing a
 callback, which will then be called whenever the user launches the app via a
@@ -40,9 +43,7 @@
 name of the native resource (xcassets on iOS or drawable on Android) that the app will display for the
 quick action.
 
-## Getting Started
+### Android
 
-For help getting started with Flutter, view our online
-[documentation](https://flutter.dev/).
-
-For help on editing plugin code, view the [documentation](https://flutter.dev/docs/development/packages-and-plugins/developing-packages#plugin).
+\* The plugin will compile and run on SDK 16+, but will be a no-op below SDK 25
+(Android 7.1).
diff --git a/packages/shared_preferences/shared_preferences/CHANGELOG.md b/packages/shared_preferences/shared_preferences/CHANGELOG.md
index 36e60cc..84566e2 100644
--- a/packages/shared_preferences/shared_preferences/CHANGELOG.md
+++ b/packages/shared_preferences/shared_preferences/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 2.0.13
 
 * Updates documentation on README.md.
diff --git a/packages/shared_preferences/shared_preferences/README.md b/packages/shared_preferences/shared_preferences/README.md
index d3295ac..03975ff 100644
--- a/packages/shared_preferences/shared_preferences/README.md
+++ b/packages/shared_preferences/shared_preferences/README.md
@@ -3,13 +3,17 @@
 [![pub package](https://img.shields.io/pub/v/shared_preferences.svg)](https://pub.dev/packages/shared_preferences)
 
 Wraps platform-specific persistent storage for simple data
-(NSUserDefaults on iOS and macOS, SharedPreferences on Android, etc.). 
+(NSUserDefaults on iOS and macOS, SharedPreferences on Android, etc.).
 Data may be persisted to disk asynchronously,
 and there is no guarantee that writes will be persisted to disk after
 returning, so this plugin must not be used for storing critical data.
 
 Supported data types are `int`, `double`, `bool`, `String` and `List<String>`.
 
+|             | Android | iOS  | Linux | macOS  | Web | Windows     |
+|-------------|---------|------|-------|--------|-----|-------------|
+| **Support** | SDK 16+ | 9.0+ | Any   | 10.11+ | Any | Any         |
+
 ## Usage
 To use this plugin, add `shared_preferences` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels).
 
@@ -17,24 +21,24 @@
 Here are small examples that show you how to use the API.
 
 #### Write data
-```dart 
+```dart
 // Obtain shared preferences.
 final prefs = await SharedPreferences.getInstance();
 
-// Save an integer value to 'counter' key. 
+// Save an integer value to 'counter' key.
 await prefs.setInt('counter', 10);
-// Save an boolean value to 'repeat' key. 
+// Save an boolean value to 'repeat' key.
 await prefs.setBool('repeat', true);
-// Save an double value to 'decimal' key. 
+// Save an double value to 'decimal' key.
 await prefs.setDouble('decimal', 1.5);
-// Save an String value to 'action' key. 
+// Save an String value to 'action' key.
 await prefs.setString('action', 'Start');
-// Save an list of strings to 'items' key. 
+// Save an list of strings to 'items' key.
 await prefs.setStringList('items', <String>['Earth', 'Moon', 'Sun']);
 ```
 
 #### Read data
-```dart 
+```dart
 // Try reading data from the 'counter' key. If it doesn't exist, returns null.
 final int? counter = prefs.getInt('counter');
 // Try reading data from the 'repeat' key. If it doesn't exist, returns null.
@@ -48,8 +52,8 @@
 ```
 
 #### Remove an entry
-```dart 
-// Remove data for the 'counter' key. 
+```dart
+// Remove data for the 'counter' key.
 final success = await prefs.remove('counter');
 ```
 
diff --git a/packages/url_launcher/url_launcher/CHANGELOG.md b/packages/url_launcher/url_launcher/CHANGELOG.md
index 1634dcd..0c38f48 100644
--- a/packages/url_launcher/url_launcher/CHANGELOG.md
+++ b/packages/url_launcher/url_launcher/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 6.0.20
 
 * Fixes a typo in `default_package` registration for Windows, macOS, and Linux.
diff --git a/packages/url_launcher/url_launcher/README.md b/packages/url_launcher/url_launcher/README.md
index a4ffb62..7f8699e 100644
--- a/packages/url_launcher/url_launcher/README.md
+++ b/packages/url_launcher/url_launcher/README.md
@@ -2,8 +2,11 @@
 
 [![pub package](https://img.shields.io/pub/v/url_launcher.svg)](https://pub.dev/packages/url_launcher)
 
-A Flutter plugin for launching a URL. Supports
-iOS, Android, web, Windows, macOS, and Linux.
+A Flutter plugin for launching a URL.
+
+|             | Android | iOS  | Linux | macOS  | Web | Windows     |
+|-------------|---------|------|-------|--------|-----|-------------|
+| **Support** | SDK 16+ | 9.0+ | Any   | 10.11+ | Any | Windows 10+ |
 
 ## Usage
 
@@ -186,5 +189,5 @@
 
 ### macOS file access configuration
 
-If you need to access files outside of your application's sandbox, you will need to have the necessary 
+If you need to access files outside of your application's sandbox, you will need to have the necessary
 [entitlements](https://docs.flutter.dev/desktop#entitlements-and-the-app-sandbox).
diff --git a/packages/video_player/video_player/CHANGELOG.md b/packages/video_player/video_player/CHANGELOG.md
index 5d56b15..d9d1983 100644
--- a/packages/video_player/video_player/CHANGELOG.md
+++ b/packages/video_player/video_player/CHANGELOG.md
@@ -1,6 +1,7 @@
 ## NEXT
 
 * Updates minimum Flutter version to 2.10.
+* Adds OS version support information to README.
 
 ## 2.3.0
 
diff --git a/packages/video_player/video_player/README.md b/packages/video_player/video_player/README.md
index 84b3ae7..91c1bde 100644
--- a/packages/video_player/video_player/README.md
+++ b/packages/video_player/video_player/README.md
@@ -4,6 +4,10 @@
 
 A Flutter plugin for iOS, Android and Web for playing back video on a Widget surface.
 
+|             | Android | iOS  | Web   |
+|-------------|---------|------|-------|
+| **Support** | SDK 16+ | 9.0+ | Any\* |
+
 ![The example app running in iOS](https://github.com/flutter/plugins/blob/master/packages/video_player/video_player/doc/demo_ipod.gif?raw=true)
 
 ## Installation
@@ -31,7 +35,7 @@
 
 > The Web platform does **not** suppport `dart:io`, so avoid using the `VideoPlayerController.file` constructor for the plugin. Using the constructor attempts to create a `VideoPlayerController.file` that will throw an `UnimplementedError`.
 
-Different web browsers may have different video-playback capabilities (supported formats, autoplay...). Check [package:video_player_web](https://pub.dev/packages/video_player_web) for more web-specific information.
+\* Different web browsers may have different video-playback capabilities (supported formats, autoplay...). Check [package:video_player_web](https://pub.dev/packages/video_player_web) for more web-specific information.
 
 The `VideoPlayerOptions.mixWithOthers` option can't be implemented in web, at least at the moment. If you use this option in web it will be silently ignored.
 
@@ -119,7 +123,7 @@
 
 You can set the playback speed on your `_controller` (instance of `VideoPlayerController`) by
 calling `_controller.setPlaybackSpeed`. `setPlaybackSpeed` takes a `double` speed value indicating
-the rate of playback for your video.  
+the rate of playback for your video.
 For example, when given a value of `2.0`, your video will play at 2x the regular playback speed
 and so on.
 
diff --git a/packages/webview_flutter/webview_flutter/CHANGELOG.md b/packages/webview_flutter/webview_flutter/CHANGELOG.md
index 34b56d6..dde4472 100644
--- a/packages/webview_flutter/webview_flutter/CHANGELOG.md
+++ b/packages/webview_flutter/webview_flutter/CHANGELOG.md
@@ -1,3 +1,7 @@
+## NEXT
+
+* Adds OS version support information to README.
+
 ## 3.0.1
 
 * Removes a duplicate Android-specific integration test.
diff --git a/packages/webview_flutter/webview_flutter/README.md b/packages/webview_flutter/webview_flutter/README.md
index 8387eb6..ffe9144 100644
--- a/packages/webview_flutter/webview_flutter/README.md
+++ b/packages/webview_flutter/webview_flutter/README.md
@@ -7,6 +7,10 @@
 On iOS the WebView widget is backed by a [WKWebView](https://developer.apple.com/documentation/webkit/wkwebview);
 On Android the WebView widget is backed by a [WebView](https://developer.android.com/reference/android/webkit/WebView).
 
+|             | Android        | iOS  |
+|-------------|----------------|------|
+| **Support** | SDK 19+ or 20+ | 9.0+ |
+
 ## Usage
 Add `webview_flutter` as a [dependency in your pubspec.yaml file](https://flutter.dev/docs/development/platform-integration/platform-channels). If you are targeting Android, make sure to read the *Android Platform Views* section below to choose the platform view mode that best suits your needs.
 
@@ -91,4 +95,4 @@
 ### Setting custom headers on POST requests
 
 Currently, setting custom headers when making a post request with the WebViewController's `loadRequest` method is not supported on Android.
-If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHTMLString` instead. 
\ No newline at end of file
+If you require this functionality, a workaround is to make the request manually, and then load the response data using `loadHTMLString` instead.
diff --git a/script/tool/CHANGELOG.md b/script/tool/CHANGELOG.md
index 45f7f52..df76e48 100644
--- a/script/tool/CHANGELOG.md
+++ b/script/tool/CHANGELOG.md
@@ -1,5 +1,6 @@
 ## NEXT
 
+- Adds a new `readme-check` command.
 - Updates `publish-plugin` command documentation.
 
 ## 0.8.2
diff --git a/script/tool/lib/src/common/repository_package.dart b/script/tool/lib/src/common/repository_package.dart
index e0c4e4a..72e7f94 100644
--- a/script/tool/lib/src/common/repository_package.dart
+++ b/script/tool/lib/src/common/repository_package.dart
@@ -48,6 +48,9 @@
   /// The package's top-level pubspec.yaml.
   File get pubspecFile => directory.childFile('pubspec.yaml');
 
+  /// The package's top-level README.
+  File get readmeFile => directory.childFile('README.md');
+
   late final Pubspec _parsedPubspec =
       Pubspec.parse(pubspecFile.readAsStringSync());
 
@@ -62,6 +65,12 @@
       directory.parent.basename != 'packages' &&
       directory.basename.startsWith(directory.parent.basename);
 
+  /// True if this appears to be the app-facing package of a federated plugin,
+  /// according to repository conventions.
+  bool get isAppFacing =>
+      directory.parent.basename != 'packages' &&
+      directory.basename == directory.parent.basename;
+
   /// True if this appears to be a platform interface package, according to
   /// repository conventions.
   bool get isPlatformInterface =>
diff --git a/script/tool/lib/src/main.dart b/script/tool/lib/src/main.dart
index 5a71a0a..aa1cf30 100644
--- a/script/tool/lib/src/main.dart
+++ b/script/tool/lib/src/main.dart
@@ -26,6 +26,7 @@
 import 'publish_check_command.dart';
 import 'publish_plugin_command.dart';
 import 'pubspec_check_command.dart';
+import 'readme_check_command.dart';
 import 'test_command.dart';
 import 'version_check_command.dart';
 import 'xcode_analyze_command.dart';
@@ -65,6 +66,7 @@
     ..addCommand(PublishCheckCommand(packagesDir))
     ..addCommand(PublishPluginCommand(packagesDir))
     ..addCommand(PubspecCheckCommand(packagesDir))
+    ..addCommand(ReadmeCheckCommand(packagesDir))
     ..addCommand(TestCommand(packagesDir))
     ..addCommand(VersionCheckCommand(packagesDir))
     ..addCommand(XcodeAnalyzeCommand(packagesDir));
diff --git a/script/tool/lib/src/readme_check_command.dart b/script/tool/lib/src/readme_check_command.dart
new file mode 100644
index 0000000..99e271c
--- /dev/null
+++ b/script/tool/lib/src/readme_check_command.dart
@@ -0,0 +1,152 @@
+// Copyright 2013 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:file/file.dart';
+import 'package:git/git.dart';
+import 'package:platform/platform.dart';
+import 'package:pubspec_parse/pubspec_parse.dart';
+import 'package:yaml/yaml.dart';
+
+import 'common/core.dart';
+import 'common/package_looping_command.dart';
+import 'common/process_runner.dart';
+import 'common/repository_package.dart';
+
+/// A command to enforce README conventions across the repository.
+class ReadmeCheckCommand extends PackageLoopingCommand {
+  /// Creates an instance of the README check command.
+  ReadmeCheckCommand(
+    Directory packagesDir, {
+    ProcessRunner processRunner = const ProcessRunner(),
+    Platform platform = const LocalPlatform(),
+    GitDir? gitDir,
+  }) : super(
+          packagesDir,
+          processRunner: processRunner,
+          platform: platform,
+          gitDir: gitDir,
+        );
+
+  // Standardized capitalizations for platforms that a plugin can support.
+  static const Map<String, String> _standardPlatformNames = <String, String>{
+    'android': 'Android',
+    'ios': 'iOS',
+    'linux': 'Linux',
+    'macos': 'macOS',
+    'web': 'Web',
+    'windows': 'Windows',
+  };
+
+  @override
+  final String name = 'readme-check';
+
+  @override
+  final String description =
+      'Checks that READMEs follow repository conventions.';
+
+  @override
+  bool get hasLongOutput => false;
+
+  @override
+  Future<PackageResult> runForPackage(RepositoryPackage package) async {
+    final File readme = package.readmeFile;
+
+    if (!readme.existsSync()) {
+      return PackageResult.fail(<String>['Missing README.md']);
+    }
+
+    final List<String> errors = <String>[];
+
+    final Pubspec pubspec = package.parsePubspec();
+    final bool isPlugin = pubspec.flutter?['plugin'] != null;
+
+    if (isPlugin && (!package.isFederated || package.isAppFacing)) {
+      final String? error = _validateSupportedPlatforms(package, pubspec);
+      if (error != null) {
+        errors.add(error);
+      }
+    }
+
+    return errors.isEmpty
+        ? PackageResult.success()
+        : PackageResult.fail(errors);
+  }
+
+  /// Validates that the plugin has a supported platforms table following the
+  /// expected format, returning an error string if any issues are found.
+  String? _validateSupportedPlatforms(
+      RepositoryPackage package, Pubspec pubspec) {
+    final List<String> contents = package.readmeFile.readAsLinesSync();
+
+    // Example table following expected format:
+    // |                | Android | iOS      | Web                    |
+    // |----------------|---------|----------|------------------------|
+    // | **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
+    final int detailsLineNumber =
+        contents.indexWhere((String line) => line.startsWith('| **Support**'));
+    if (detailsLineNumber == -1) {
+      return 'No OS support table found';
+    }
+    final int osLineNumber = detailsLineNumber - 2;
+    if (osLineNumber < 0 || !contents[osLineNumber].startsWith('|')) {
+      return 'OS support table does not have the expected header format';
+    }
+
+    // Utility method to convert an iterable of strings to a case-insensitive
+    // sorted, comma-separated string of its elements.
+    String sortedListString(Iterable<String> entries) {
+      final List<String> entryList = entries.toList();
+      entryList.sort(
+          (String a, String b) => a.toLowerCase().compareTo(b.toLowerCase()));
+      return entryList.join(', ');
+    }
+
+    // Validate that the supported OS lists match.
+    final dynamic platformsEntry = pubspec.flutter!['plugin']!['platforms'];
+    if (platformsEntry == null) {
+      logWarning('Plugin not support any platforms');
+      return null;
+    }
+    final YamlMap platformSupportMaps = platformsEntry as YamlMap;
+    final Set<String> actuallySupportedPlatform =
+        platformSupportMaps.keys.toSet().cast<String>();
+    final Iterable<String> documentedPlatforms = contents[osLineNumber]
+        .split('|')
+        .map((String entry) => entry.trim())
+        .where((String entry) => entry.isNotEmpty);
+    final Set<String> documentedPlatformsLowercase =
+        documentedPlatforms.map((String entry) => entry.toLowerCase()).toSet();
+    if (actuallySupportedPlatform.length != documentedPlatforms.length ||
+        actuallySupportedPlatform
+                .intersection(documentedPlatformsLowercase)
+                .length !=
+            actuallySupportedPlatform.length) {
+      printError('''
+${indentation}OS support table does not match supported platforms:
+${indentation * 2}Actual:     ${sortedListString(actuallySupportedPlatform)}
+${indentation * 2}Documented: ${sortedListString(documentedPlatformsLowercase)}
+''');
+      return 'Incorrect OS support table';
+    }
+
+    // Enforce a standard set of capitalizations for the OS headings.
+    final Iterable<String> incorrectCapitalizations = documentedPlatforms
+        .toSet()
+        .difference(_standardPlatformNames.values.toSet());
+    if (incorrectCapitalizations.isNotEmpty) {
+      final Iterable<String> expectedVersions = incorrectCapitalizations
+          .map((String name) => _standardPlatformNames[name.toLowerCase()]!);
+      printError('''
+${indentation}Incorrect OS capitalization: ${sortedListString(incorrectCapitalizations)}
+${indentation * 2}Please use standard capitalizations: ${sortedListString(expectedVersions)}
+''');
+      return 'Incorrect OS support formatting';
+    }
+
+    // TODO(stuartmorgan): Add validation that the minimums in the table are
+    // consistent with what the current implementations require. See
+    // https://github.com/flutter/flutter/issues/84200
+    return null;
+  }
+}
diff --git a/script/tool/test/common/repository_package_test.dart b/script/tool/test/common/repository_package_test.dart
index 29e3b58..0a8ea36 100644
--- a/script/tool/test/common/repository_package_test.dart
+++ b/script/tool/test/common/repository_package_test.dart
@@ -126,6 +126,7 @@
     test('all return false for a simple plugin', () {
       final Directory plugin = createFakePlugin('a_plugin', packagesDir);
       expect(RepositoryPackage(plugin).isFederated, false);
+      expect(RepositoryPackage(plugin).isAppFacing, false);
       expect(RepositoryPackage(plugin).isPlatformInterface, false);
       expect(RepositoryPackage(plugin).isFederated, false);
     });
@@ -134,6 +135,7 @@
       final Directory plugin =
           createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin'));
       expect(RepositoryPackage(plugin).isFederated, true);
+      expect(RepositoryPackage(plugin).isAppFacing, true);
       expect(RepositoryPackage(plugin).isPlatformInterface, false);
       expect(RepositoryPackage(plugin).isPlatformImplementation, false);
     });
@@ -142,6 +144,7 @@
       final Directory plugin = createFakePlugin('a_plugin_platform_interface',
           packagesDir.childDirectory('a_plugin'));
       expect(RepositoryPackage(plugin).isFederated, true);
+      expect(RepositoryPackage(plugin).isAppFacing, false);
       expect(RepositoryPackage(plugin).isPlatformInterface, true);
       expect(RepositoryPackage(plugin).isPlatformImplementation, false);
     });
@@ -152,6 +155,7 @@
       final Directory plugin = createFakePlugin(
           'a_plugin_foo', packagesDir.childDirectory('a_plugin'));
       expect(RepositoryPackage(plugin).isFederated, true);
+      expect(RepositoryPackage(plugin).isAppFacing, false);
       expect(RepositoryPackage(plugin).isPlatformInterface, false);
       expect(RepositoryPackage(plugin).isPlatformImplementation, true);
     });
diff --git a/script/tool/test/readme_check_command_test.dart b/script/tool/test/readme_check_command_test.dart
new file mode 100644
index 0000000..aec2fa0
--- /dev/null
+++ b/script/tool/test/readme_check_command_test.dart
@@ -0,0 +1,278 @@
+// Copyright 2013 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:args/command_runner.dart';
+import 'package:file/file.dart';
+import 'package:file/memory.dart';
+import 'package:flutter_plugin_tools/src/common/core.dart';
+import 'package:flutter_plugin_tools/src/common/plugin_utils.dart';
+import 'package:flutter_plugin_tools/src/readme_check_command.dart';
+import 'package:test/test.dart';
+
+import 'mocks.dart';
+import 'util.dart';
+
+void main() {
+  late CommandRunner<void> runner;
+  late RecordingProcessRunner processRunner;
+  late FileSystem fileSystem;
+  late MockPlatform mockPlatform;
+  late Directory packagesDir;
+
+  setUp(() {
+    fileSystem = MemoryFileSystem();
+    mockPlatform = MockPlatform();
+    packagesDir = fileSystem.currentDirectory.childDirectory('packages');
+    createPackagesDirectory(parentDir: packagesDir.parent);
+    processRunner = RecordingProcessRunner();
+    final ReadmeCheckCommand command = ReadmeCheckCommand(
+      packagesDir,
+      processRunner: processRunner,
+      platform: mockPlatform,
+    );
+
+    runner = CommandRunner<void>(
+        'readme_check_command', 'Test for readme_check_command');
+    runner.addCommand(command);
+  });
+
+  test('fails when README is missing', () async {
+    createFakePackage('a_package', packagesDir);
+
+    Error? commandError;
+    final List<String> output = await runCapturingPrint(
+        runner, <String>['readme-check'], errorHandler: (Error e) {
+      commandError = e;
+    });
+
+    expect(commandError, isA<ToolExit>());
+    expect(
+      output,
+      containsAllInOrder(<Matcher>[
+        contains('Missing README.md'),
+      ]),
+    );
+  });
+
+  group('plugin OS support', () {
+    test(
+        'does not check support table for anything other than app-facing plugin packages',
+        () async {
+      const String federatedPluginName = 'a_federated_plugin';
+      final Directory federatedDir =
+          packagesDir.childDirectory(federatedPluginName);
+      final List<Directory> packageDirectories = <Directory>[
+        // A non-plugin package.
+        createFakePackage('a_package', packagesDir),
+        // Non-app-facing parts of a federated plugin.
+        createFakePlugin(
+            '${federatedPluginName}_platform_interface', federatedDir),
+        createFakePlugin('${federatedPluginName}_android', federatedDir),
+      ];
+
+      for (final Directory package in packageDirectories) {
+        package.childFile('README.md').writeAsStringSync('''
+A very useful package.
+''');
+      }
+
+      final List<String> output = await runCapturingPrint(runner, <String>[
+        'readme-check',
+      ]);
+
+      expect(
+        output,
+        containsAll(<Matcher>[
+          contains('Running for a_package...'),
+          contains('Running for a_federated_plugin_platform_interface...'),
+          contains('Running for a_federated_plugin_android...'),
+          contains('No issues found!'),
+        ]),
+      );
+    });
+
+    test('fails when non-federated plugin is missing an OS support table',
+        () async {
+      final Directory pluginDir = createFakePlugin('a_plugin', packagesDir);
+
+      pluginDir.childFile('README.md').writeAsStringSync('''
+A very useful plugin.
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['readme-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('No OS support table found'),
+        ]),
+      );
+    });
+
+    test(
+        'fails when app-facing part of a federated plugin is missing an OS support table',
+        () async {
+      final Directory pluginDir =
+          createFakePlugin('a_plugin', packagesDir.childDirectory('a_plugin'));
+
+      pluginDir.childFile('README.md').writeAsStringSync('''
+A very useful plugin.
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['readme-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('No OS support table found'),
+        ]),
+      );
+    });
+
+    test('fails the OS support table is missing the header', () async {
+      final Directory pluginDir = createFakePlugin('a_plugin', packagesDir);
+
+      pluginDir.childFile('README.md').writeAsStringSync('''
+A very useful plugin.
+
+| **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['readme-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('OS support table does not have the expected header format'),
+        ]),
+      );
+    });
+
+    test('fails if the OS support table is missing a supported OS', () async {
+      final Directory pluginDir = createFakePlugin(
+        'a_plugin',
+        packagesDir,
+        platformSupport: <String, PlatformDetails>{
+          platformAndroid: const PlatformDetails(PlatformSupport.inline),
+          platformIOS: const PlatformDetails(PlatformSupport.inline),
+          platformWeb: const PlatformDetails(PlatformSupport.inline),
+        },
+      );
+
+      pluginDir.childFile('README.md').writeAsStringSync('''
+A very useful plugin.
+
+|                | Android | iOS      |
+|----------------|---------|----------|
+| **Support**    | SDK 21+ | iOS 10+* |
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['readme-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('  OS support table does not match supported platforms:\n'
+              '    Actual:     android, ios, web\n'
+              '    Documented: android, ios'),
+          contains('Incorrect OS support table'),
+        ]),
+      );
+    });
+
+    test('fails if the OS support table lists an extra OS', () async {
+      final Directory pluginDir = createFakePlugin(
+        'a_plugin',
+        packagesDir,
+        platformSupport: <String, PlatformDetails>{
+          platformAndroid: const PlatformDetails(PlatformSupport.inline),
+          platformIOS: const PlatformDetails(PlatformSupport.inline),
+        },
+      );
+
+      pluginDir.childFile('README.md').writeAsStringSync('''
+A very useful plugin.
+
+|                | Android | iOS      | Web                    |
+|----------------|---------|----------|------------------------|
+| **Support**    | SDK 21+ | iOS 10+* | [See `camera_web `][1] |
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['readme-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('  OS support table does not match supported platforms:\n'
+              '    Actual:     android, ios\n'
+              '    Documented: android, ios, web'),
+          contains('Incorrect OS support table'),
+        ]),
+      );
+    });
+
+    test('fails if the OS support table has unexpected OS formatting',
+        () async {
+      final Directory pluginDir = createFakePlugin(
+        'a_plugin',
+        packagesDir,
+        platformSupport: <String, PlatformDetails>{
+          platformAndroid: const PlatformDetails(PlatformSupport.inline),
+          platformIOS: const PlatformDetails(PlatformSupport.inline),
+          platformMacOS: const PlatformDetails(PlatformSupport.inline),
+          platformWeb: const PlatformDetails(PlatformSupport.inline),
+        },
+      );
+
+      pluginDir.childFile('README.md').writeAsStringSync('''
+A very useful plugin.
+
+|                | android | ios      | MacOS | web                    |
+|----------------|---------|----------|-------|------------------------|
+| **Support**    | SDK 21+ | iOS 10+* | 10.11 | [See `camera_web `][1] |
+''');
+
+      Error? commandError;
+      final List<String> output = await runCapturingPrint(
+          runner, <String>['readme-check'], errorHandler: (Error e) {
+        commandError = e;
+      });
+
+      expect(commandError, isA<ToolExit>());
+      expect(
+        output,
+        containsAllInOrder(<Matcher>[
+          contains('  Incorrect OS capitalization: android, ios, MacOS, web\n'
+              '    Please use standard capitalizations: Android, iOS, macOS, Web\n'),
+          contains('Incorrect OS support formatting'),
+        ]),
+      );
+    });
+  });
+}