Improve error message when a plugin sets an invalid android package (#48104)

diff --git a/packages/flutter_tools/test/general.shard/forbidden_imports_test.dart b/packages/flutter_tools/test/general.shard/forbidden_imports_test.dart
index eb771ab..3aab675 100644
--- a/packages/flutter_tools/test/general.shard/forbidden_imports_test.dart
+++ b/packages/flutter_tools/test/general.shard/forbidden_imports_test.dart
@@ -111,12 +111,15 @@
   });
 
   test('no unauthorized imports of package:path', () {
-    final String whitelistedPath = globals.fs.path.join(flutterTools, 'lib', 'src', 'build_runner', 'web_compilation_delegate.dart');
+    final List<String> whitelistedPath = <String>[
+      globals.fs.path.join(flutterTools, 'lib', 'src', 'build_runner', 'web_compilation_delegate.dart'),
+      globals.fs.path.join(flutterTools, 'test', 'general.shard', 'platform_plugins_test.dart'),
+    ];
     for (final String dirName in <String>['lib', 'bin', 'test']) {
-      final Iterable<File> files = globals.fs.directory(globals.fs.path.join(flutterTools, dirName))
+      final Iterable<File> files =  globals.fs.directory(globals.fs.path.join(flutterTools, dirName))
         .listSync(recursive: true)
         .where(_isDartFile)
-        .where((FileSystemEntity entity) => entity.path != whitelistedPath)
+        .where((FileSystemEntity entity) => !whitelistedPath.contains(entity.path))
         .map(_asFile);
       for (final File file in files) {
         for (final String line in file.readAsLinesSync()) {
diff --git a/packages/flutter_tools/test/general.shard/platform_plugins_test.dart b/packages/flutter_tools/test/general.shard/platform_plugins_test.dart
new file mode 100644
index 0000000..cbf998c
--- /dev/null
+++ b/packages/flutter_tools/test/general.shard/platform_plugins_test.dart
@@ -0,0 +1,67 @@
+// Copyright 2014 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:flutter_tools/src/platform_plugins.dart';
+import 'package:mockito/mockito.dart';
+import 'package:path/path.dart' as p;
+
+import '../src/common.dart';
+import '../src/context.dart';
+
+void main() {
+  group('AndroidPlugin', () {
+    MockFileSystem mockFileSystem;
+    MockPathContext pathContext;
+
+    setUp(() {
+      pathContext = MockPathContext();
+      when(pathContext.separator).thenReturn('/');
+
+      mockFileSystem = MockFileSystem();
+      when(mockFileSystem.path).thenReturn(pathContext);
+    });
+
+    testUsingContext('throws tool exit if the plugin main class can\'t be read', () {
+      when(pathContext.join('.pub_cache/plugin_a', 'android', 'src', 'main'))
+        .thenReturn('.pub_cache/plugin_a/android/src/main');
+
+      when(pathContext.join('.pub_cache/plugin_a/android/src/main', 'java', 'com/company', 'PluginA.java'))
+        .thenReturn('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java');
+
+      when(pathContext.join('.pub_cache/plugin_a/android/src/main', 'kotlin', 'com/company', 'PluginA.kt'))
+        .thenReturn('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt');
+
+      final MockFile pluginJavaMainClass = MockFile();
+      when(pluginJavaMainClass.existsSync()).thenReturn(true);
+      when(pluginJavaMainClass.readAsStringSync()).thenThrow(const FileSystemException());
+      when(mockFileSystem.file('.pub_cache/plugin_a/android/src/main/java/com/company/PluginA.java'))
+        .thenReturn(pluginJavaMainClass);
+
+      final MockFile pluginKotlinMainClass = MockFile();
+      when(pluginKotlinMainClass.existsSync()).thenReturn(false);
+      when(mockFileSystem.file('.pub_cache/plugin_a/android/src/main/kotlin/com/company/PluginA.kt'))
+        .thenReturn(pluginKotlinMainClass);
+
+      expect(() {
+        AndroidPlugin(
+          name: 'pluginA',
+          package: 'com.company',
+          pluginClass: 'PluginA',
+          pluginPath: '.pub_cache/plugin_a',
+        ).toMap();
+      }, throwsToolExit(
+        message: 'Couldn\'t read file null even though it exists. '
+                 'Please verify that this file has read permission and try again.'
+      ));
+    }, overrides: <Type, Generator>{
+      FileSystem: () => mockFileSystem,
+      ProcessManager: () => FakeProcessManager.any(),
+    });
+  });
+}
+
+class MockFile extends Mock implements File {}
+class MockFileSystem extends Mock implements FileSystem {}
+class MockPathContext extends Mock implements p.Context {}
diff --git a/packages/flutter_tools/test/general.shard/plugins_test.dart b/packages/flutter_tools/test/general.shard/plugins_test.dart
index 2117656..81bde7f 100644
--- a/packages/flutter_tools/test/general.shard/plugins_test.dart
+++ b/packages/flutter_tools/test/general.shard/plugins_test.dart
@@ -146,6 +146,36 @@
         );
     }
 
+    void createPluginWithInvalidAndroidPackage() {
+      final Directory pluginUsingJavaAndNewEmbeddingDir =
+              fs.systemTempDirectory.createTempSync('flutter_plugin_invalid_package.');
+      pluginUsingJavaAndNewEmbeddingDir
+        .childFile('pubspec.yaml')
+        .writeAsStringSync('''
+flutter:
+  plugin:
+    androidPackage: plugin1.invalid
+    pluginClass: UseNewEmbedding
+              ''');
+      pluginUsingJavaAndNewEmbeddingDir
+        .childDirectory('android')
+        .childDirectory('src')
+        .childDirectory('main')
+        .childDirectory('java')
+        .childDirectory('plugin1')
+        .childDirectory('correct')
+        .childFile('UseNewEmbedding.java')
+        ..createSync(recursive: true)
+        ..writeAsStringSync('import io.flutter.embedding.engine.plugins.FlutterPlugin;');
+
+      flutterProject.directory
+        .childFile('.packages')
+        .writeAsStringSync(
+          'plugin1:${pluginUsingJavaAndNewEmbeddingDir.childDirectory('lib').uri.toString()}\n',
+          mode: FileMode.append,
+        );
+    }
+
     void createNewKotlinPlugin2() {
       final Directory pluginUsingKotlinAndNewEmbeddingDir =
           fs.systemTempDirectory.createTempSync('flutter_plugin_using_kotlin_and_new_embedding_dir.');
@@ -556,6 +586,33 @@
         XcodeProjectInterpreter: () => xcodeProjectInterpreter,
       });
 
+      // Issue: https://github.com/flutter/flutter/issues/47803
+      testUsingContext('exits the tool if a plugin sets an invalid android package in pubspec.yaml', () async {
+        when(flutterProject.isModule).thenReturn(false);
+        when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
+
+        createPluginWithInvalidAndroidPackage();
+
+        await expectLater(
+          () async {
+            await injectPlugins(flutterProject);
+          },
+          throwsToolExit(
+            message: 'The plugin `plugin1` doesn\'t have a main class defined in '
+                     '/.tmp_rand2/flutter_plugin_invalid_package.rand2/android/src/main/java/plugin1/invalid/UseNewEmbedding.java or '
+                     '/.tmp_rand2/flutter_plugin_invalid_package.rand2/android/src/main/kotlin/plugin1/invalid/UseNewEmbedding.kt. '
+                     'This is likely to due to an incorrect `androidPackage: plugin1.invalid` or `mainClass` entry in the plugin\'s pubspec.yaml.\n'
+                     'If you are the author of this plugin, fix the `androidPackage` entry or move the main class to any of locations used above. '
+                     'Otherwise, please contact the author of this plugin and consider using a different plugin in the meanwhile.',
+          ),
+        );
+      }, overrides: <Type, Generator>{
+        FileSystem: () => fs,
+        ProcessManager: () => FakeProcessManager.any(),
+        FeatureFlags: () => featureFlags,
+        XcodeProjectInterpreter: () => xcodeProjectInterpreter,
+      });
+
       testUsingContext('old embedding app uses a plugin that supports v1 and v2 embedding', () async {
         when(flutterProject.isModule).thenReturn(false);
         when(androidProject.getEmbeddingVersion()).thenReturn(AndroidEmbeddingVersion.v1);
@@ -776,6 +833,7 @@
 class MockFeatureFlags extends Mock implements FeatureFlags {}
 class MockFlutterProject extends Mock implements FlutterProject {}
 class MockFile extends Mock implements File {}
+class MockFileSystem extends Mock implements FileSystem {}
 class MockIosProject extends Mock implements IosProject {}
 class MockMacOSProject extends Mock implements MacOSProject {}
 class MockXcodeProjectInterpreter extends Mock implements XcodeProjectInterpreter {}