[flutter_tools] Don't crash on non-UTF-8 plugin pubspec.yaml (#188976)

Fixes https://github.com/flutter/flutter/issues/188970

`_pluginFromPackage` caught only `YamlException` around the plugin's
`pubspec.yaml` read, so a pubspec containing non-UTF-8 bytes threw an
uncaught `FileSystemException` and crashed the tool. This mirrors the
sibling pubspec-cache reader (added in #184528), catching
`FileSystemException` and skipping the unreadable package.

## Tests
Added a regression test in `plugins_test.dart` that corrupts a plugin's
`pubspec.yaml` with invalid UTF-8 and asserts the tool skips it instead
of crashing. Verified it fails without the fix
(`FileSystemException: Invalid UTF-8 byte`) and passes with it.

## Design note
`dart:io` surfaces UTF-8 decode failures as a generic
`FileSystemException` with no narrower subtype, so this catches
`FileSystemException`, consistent with the sibling `buildPubspecCache`.
Genuine I/O errors on an existing pubspec are therefore also skipped
rather than crashing, which matches existing behavior and is preferable
to failing every command over one unreadable transitive dependency.
diff --git a/packages/flutter_tools/lib/src/flutter_plugins.dart b/packages/flutter_tools/lib/src/flutter_plugins.dart
index ca9dc32..1d52046 100644
--- a/packages/flutter_tools/lib/src/flutter_plugins.dart
+++ b/packages/flutter_tools/lib/src/flutter_plugins.dart
@@ -125,6 +125,9 @@
     } on YamlException catch (err) {
       globals.printTrace('Failed to parse plugin manifest for $name: $err');
       // Do nothing, potentially not a plugin.
+    } on FileSystemException catch (err) {
+      globals.printTrace('Failed to read plugin manifest for $name: $err');
+      // Do nothing, potentially not a plugin.
     }
   }
   if (pubspec == null) {
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 4ac9a79..1d2e063 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -4,6 +4,7 @@
 
 import 'dart:convert';
 import 'dart:io';
+import 'dart:typed_data';
 
 import 'package:file/file.dart';
 import 'package:file/memory.dart';
@@ -483,6 +484,36 @@
       );
 
       testUsingContext(
+        'does not crash when a plugin pubspec.yaml is not valid UTF-8',
+        () async {
+          // Regression test for https://github.com/flutter/flutter/issues/188970.
+          final List<Directory> pluginDirs = createFakePlugins(fs, <String>[
+            'good_plugin',
+            'bad_plugin',
+          ]);
+          // Write bytes that are not valid UTF-8, so readAsString throws a FileSystemException.
+          pluginDirs[1].childFile('pubspec.yaml').writeAsBytesSync(
+            Uint8List.fromList(<int>[0xff, 0xfe, 0xfd]),
+          );
+
+          // The tool must not crash when a plugin's pubspec.yaml cannot be read.
+          final Future<List<Plugin>> pluginsFuture = findPlugins(flutterProject);
+          await expectLater(pluginsFuture, completes);
+
+          // The unreadable plugin is skipped, but the readable one is still found.
+          final List<Plugin> plugins = await pluginsFuture;
+          final pluginNames = <String>[for (final Plugin plugin in plugins) plugin.name];
+          expect(pluginNames, contains('good_plugin'));
+          expect(pluginNames, isNot(contains('bad_plugin')));
+        },
+        overrides: <Type, Generator>{
+          FileSystem: () => fs,
+          ProcessManager: () => FakeProcessManager.any(),
+          Pub: ThrowingPub.new,
+        },
+      );
+
+      testUsingContext(
         'Refreshing the plugin list updates .flutter-plugins-dependencies if the plugins changed',
         () async {
           // Refresh the plugin list (we have no plugins).