[flutter_tools] Refactor Android Studio Detection on Windows (support 4.x and 202x releases detection) (#86624)

diff --git a/packages/flutter_tools/lib/src/android/android_studio.dart b/packages/flutter_tools/lib/src/android/android_studio.dart
index fcdb9df..c15cad0 100644
--- a/packages/flutter_tools/lib/src/android/android_studio.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio.dart
@@ -10,6 +10,7 @@
 import '../convert.dart';
 import '../globals_null_migrated.dart' as globals;
 import '../ios/plist_parser.dart';
+import 'android_studio_validator.dart';
 
 // Android Studio layout:
 
@@ -373,51 +374,37 @@
       }
     }
 
-    // 4.1 has a different location for AndroidStudio installs on Windows.
+    // Discover Android Studio > 4.1
     if (globals.platform.isWindows && globals.platform.environment.containsKey('LOCALAPPDATA')) {
-      final File homeDot = globals.fs.file(globals.fs.path.join(
-        globals.platform.environment['LOCALAPPDATA']!,
-        'Google',
-        'AndroidStudio4.1',
-        '.home',
-      ));
-      if (homeDot.existsSync()) {
-        final String installPath = homeDot.readAsStringSync();
-        if (globals.fs.isDirectorySync(installPath)) {
-          final AndroidStudio studio = AndroidStudio(
-            installPath,
-            version: Version(4, 1, 0),
-            studioAppName: 'Android Studio 4.1',
-          );
-          if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
-            studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
-            studios.add(studio);
-          }
-        }
+      final Directory cacheDir = globals.fs.directory(globals.fs.path.join(globals.platform.environment['LOCALAPPDATA']!, 'Google'));
+      if (!cacheDir.existsSync()) {
+        return studios;
       }
-    }
+      for (final Directory dir in cacheDir.listSync().whereType<Directory>()) {
+        final String name  = globals.fs.path.basename(dir.path);
+        AndroidStudioValidator.idToTitle.forEach((String id, String title) {
+          if (name.startsWith(id)) {
+            final String version = name.substring(id.length);
+            String? installPath;
 
-    // 4.2 has a different location for AndroidStudio installs on Windows.
-    if (globals.platform.isWindows && globals.platform.environment.containsKey('LOCALAPPDATA')) {
-      final File homeDot = globals.fs.file(globals.fs.path.join(
-        globals.platform.environment['LOCALAPPDATA']!,
-        'Google',
-        'AndroidStudio4.2',
-        '.home',
-      ));
-      if (homeDot.existsSync()) {
-        final String installPath = homeDot.readAsStringSync();
-        if (globals.fs.isDirectorySync(installPath)) {
-          final AndroidStudio studio = AndroidStudio(
-            installPath,
-            version: Version(4, 2, 0),
-            studioAppName: 'Android Studio 4.2',
-          );
-          if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
-            studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
-            studios.add(studio);
+            try {
+              installPath = globals.fs.file(globals.fs.path.join(dir.path, '.home')).readAsStringSync();
+            } on FileSystemException {
+              // ignored
+            }
+            if (installPath != null && globals.fs.isDirectorySync(installPath)) {
+              final AndroidStudio studio = AndroidStudio(
+                installPath,
+                version: Version.parse(version),
+                studioAppName: title,
+              );
+              if (studio != null && !_hasStudioAt(studio.directory, newerThan: studio.version)) {
+                studios.removeWhere((AndroidStudio other) => other.directory == studio.directory);
+                studios.add(studio);
+              }
+            }
           }
-        }
+        });
       }
     }
 
diff --git a/packages/flutter_tools/lib/src/android/android_studio_validator.dart b/packages/flutter_tools/lib/src/android/android_studio_validator.dart
index 013cc7e..8ff88ac 100644
--- a/packages/flutter_tools/lib/src/android/android_studio_validator.dart
+++ b/packages/flutter_tools/lib/src/android/android_studio_validator.dart
@@ -11,6 +11,11 @@
 import '../intellij/intellij.dart';
 import 'android_studio.dart';
 
+const String _androidStudioTitle = 'Android Studio';
+const String _androidStudioId = 'AndroidStudio';
+const String _androidStudioPreviewTitle = 'Android Studio Preview';
+const String _androidStudioPreviewId = 'AndroidStudioPreview';
+
 class AndroidStudioValidator extends DoctorValidator {
   AndroidStudioValidator(this._studio, { required FileSystem fileSystem })
     : _fileSystem = fileSystem,
@@ -19,6 +24,11 @@
   final AndroidStudio _studio;
   final FileSystem _fileSystem;
 
+  static const Map<String, String> idToTitle = <String, String>{
+    _androidStudioId: _androidStudioTitle,
+    _androidStudioPreviewId: _androidStudioPreviewTitle,
+  };
+
   static List<DoctorValidator> allValidators(Config config, Platform platform, FileSystem fileSystem, UserMessages userMessages) {
     final List<AndroidStudio> studios = AndroidStudio.allInstalled();
     return <DoctorValidator>[
diff --git a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart
index 177574f..7ccecee 100644
--- a/packages/flutter_tools/test/general.shard/android/android_studio_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/android_studio_test.dart
@@ -402,7 +402,7 @@
     final AndroidStudio studio = AndroidStudio.allInstalled().single;
 
     expect(studio.version, Version(4, 1, 0));
-    expect(studio.studioAppName, 'Android Studio 4.1');
+    expect(studio.studioAppName, 'Android Studio');
   }, overrides: <Type, Generator>{
     Platform: () => windowsPlatform,
     FileSystem: () => windowsFileSystem,
@@ -420,7 +420,25 @@
     final AndroidStudio studio = AndroidStudio.allInstalled().single;
 
     expect(studio.version, Version(4, 2, 0));
-    expect(studio.studioAppName, 'Android Studio 4.2');
+    expect(studio.studioAppName, 'Android Studio');
+  }, overrides: <Type, Generator>{
+    Platform: () => windowsPlatform,
+    FileSystem: () => windowsFileSystem,
+    ProcessManager: () => FakeProcessManager.any(),
+  });
+
+  testUsingContext('Can discover Android Studio 2020.3 location on Windows', () {
+    windowsFileSystem.file(r'C:\Users\Dash\AppData\Local\Google\AndroidStudio2020.3\.home')
+      ..createSync(recursive: true)
+      ..writeAsStringSync(r'C:\Program Files\AndroidStudio');
+    windowsFileSystem
+      .directory(r'C:\Program Files\AndroidStudio')
+      .createSync(recursive: true);
+
+    final AndroidStudio studio = AndroidStudio.allInstalled().single;
+
+    expect(studio.version, Version(2020, 3, 0));
+    expect(studio.studioAppName, 'Android Studio');
   }, overrides: <Type, Generator>{
     Platform: () => windowsPlatform,
     FileSystem: () => windowsFileSystem,
@@ -463,6 +481,24 @@
     ProcessManager: () => FakeProcessManager.any(),
   });
 
+  testUsingContext('Does not discover Android Studio 2020.3 location on Windows if LOCALAPPDATA is null', () {
+    windowsFileSystem.file(r'C:\Users\Dash\AppData\Local\Google\AndroidStudio2020.3\.home')
+      ..createSync(recursive: true)
+      ..writeAsStringSync(r'C:\Program Files\AndroidStudio');
+    windowsFileSystem
+      .directory(r'C:\Program Files\AndroidStudio')
+      .createSync(recursive: true);
+
+    expect(AndroidStudio.allInstalled(), isEmpty);
+  }, overrides: <Type, Generator>{
+    Platform: () => FakePlatform(
+      operatingSystem: 'windows',
+      environment: <String, String>{}, // Does not include LOCALAPPDATA
+    ),
+    FileSystem: () => windowsFileSystem,
+    ProcessManager: () => FakeProcessManager.any(),
+  });
+
   group('Installation detection on Linux', () {
     FileSystemUtils fsUtils;