[flutter_tools] Lazily initialize AndroidSdk platform and build-tools discovery (#191972)

## Description

This PR optimizes `AndroidSdk` initialization by deferring expensive
synchronous filesystem operations out of the constructor.

### Why this is needed now
As part of the modular dependency injection migration
([#190724](https://github.com/flutter/flutter/pull/190724)), context
objects like `AndroidContext` are created upfront during
`ToolDependencies.bootstrap` / runner initialization rather than via the
old global ambient context (`AppContext`).

Previously, `AndroidSdk` was initialized lazily only when a command or
service queried `globals.androidSdk` or `context.get<AndroidSdk>()`.
With upfront `AndroidContext` creation, `AndroidSdk.locateAndroidSdk()`
runs on tool startup for every command. Because the `AndroidSdk`
constructor eagerly executed `reinitialize()`, it performed synchronous
directory listings across `build-tools` and `platforms` directories and
read `build.prop` files on disk. This introduced unnecessary disk I/O
and latency to tool startup, including for commands completely unrelated
to Android (such as `flutter config` or iOS-specific workflows).

### Changes
- Defers synchronous scanning of build-tools and platform directories
(`reinitialize()`) out of the `AndroidSdk` constructor so that object
instantiation is fast and side-effect free.
- `sdkVersions` and `latestVersion` are evaluated lazily on first
access.
- Retains `_fileSystem` on the instance so that explicit calls to
`reinitialize()` continue to refresh platform and version state with the
expected filesystem.

## Related Issues
Part of [#47161](https://github.com/flutter/flutter/issues/47161)
(flutter_tools modular dependency injection migration).

## Tests
- Updated existing test in
`packages/flutter_tools/test/general.shard/android/android_sdk_test.dart`
(`constructing an AndroidSdk handles no matching lines in build.prop`)
to access `sdk.latestVersion` so that `build.prop` parsing
error-handling is executed during lazy initialization.
- Added unit tests verifying:
- Constructor instantiation does not initialize `sdkVersions` or
`latestVersion` or scan platform directories.
- `sdkVersions` and `latestVersion` evaluate lazily on first access
across independent instances.
- Calling `reinitialize()` updates `sdkVersions` and `latestVersion` and
correctly falls back to stored `_fileSystem`.
diff --git a/packages/flutter_tools/lib/src/android/android_sdk.dart b/packages/flutter_tools/lib/src/android/android_sdk.dart
index c89a695..ab72c1e 100644
--- a/packages/flutter_tools/lib/src/android/android_sdk.dart
+++ b/packages/flutter_tools/lib/src/android/android_sdk.dart
@@ -44,17 +44,24 @@
 // $ANDROID_HOME/platforms/android-23/android.jar
 // $ANDROID_HOME/platforms/android-N/android.jar
 class AndroidSdk {
-  AndroidSdk(this.directory, {this._java, FileSystem? fileSystem}) {
-    reinitialize(fileSystem: fileSystem);
-  }
+  AndroidSdk(this.directory, {this._java});
 
   /// The Android SDK root directory.
   final Directory directory;
 
   final Java? _java;
 
-  var _sdkVersions = <AndroidSdkVersion>[];
+  List<AndroidSdkVersion> _sdkVersions = <AndroidSdkVersion>[];
   AndroidSdkVersion? _latestVersion;
+  bool _reinitialized = false;
+
+  void _ensureInitialized() {
+    if (_reinitialized) {
+      return;
+    }
+    _reinitialized = true;
+    reinitialize();
+  }
 
   /// Whether the `cmdline-tools` directory exists in the Android SDK.
   ///
@@ -170,9 +177,15 @@
     return globals.fs.isDirectorySync(globals.fs.path.join(dir, 'licenses'));
   }
 
-  List<AndroidSdkVersion> get sdkVersions => _sdkVersions;
+  List<AndroidSdkVersion> get sdkVersions {
+    _ensureInitialized();
+    return _sdkVersions;
+  }
 
-  AndroidSdkVersion? get latestVersion => _latestVersion;
+  AndroidSdkVersion? get latestVersion {
+    _ensureInitialized();
+    return _latestVersion;
+  }
 
   late final String? adbPath = getPlatformToolsPath(globals.platform.isWindows ? 'adb.exe' : 'adb');
 
@@ -451,7 +464,8 @@
   ///
   /// This method should be called in a case where the tooling may have updated
   /// SDK artifacts, such as after running a gradle build.
-  void reinitialize({FileSystem? fileSystem}) {
+  void reinitialize() {
+    _reinitialized = true;
     var buildTools = <Version>[]; // 19.1.0, 22.0.1, ...
 
     final Directory buildToolsDir = directory.childDirectory('build-tools');
@@ -514,10 +528,10 @@
 
           return AndroidSdkVersion._(
             this,
-            sdkLevel: platformVersion,
-            platformName: platformName,
             buildToolsVersion: buildToolsVersion,
-            fileSystem: fileSystem ?? globals.fs,
+            fileSystem: directory.fileSystem,
+            platformName: platformName,
+            sdkLevel: platformVersion,
           );
         })
         .whereType<AndroidSdkVersion>()
diff --git a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart
index 42e9c94..f3dc2a9 100644
--- a/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/android_gradle_builder_test.dart
@@ -117,7 +117,6 @@
                   AndroidSdk(
                     (fileSystem ?? globals.fs).directory(missingSdkPath()),
                     java: FakeJava(),
-                    fileSystem: fileSystem ?? globals.fs,
                   ),
             ),
         analytics: analytics ?? fakeAnalytics,
@@ -133,11 +132,7 @@
         description,
         body,
         overrides: <Type, Generator>{
-          AndroidSdk: () => AndroidSdk(
-            fileSystem.directory(missingSdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          ),
+          AndroidSdk: () => AndroidSdk(fileSystem.directory(missingSdkPath()), java: FakeJava()),
           ProcessManager: () => processManager,
           ...overrides,
         },
@@ -230,11 +225,7 @@
               .childFile('source.properties')
               .createSync(recursive: true);
           fileSystem.directory(ndkPath('29.0.13846066-bad')).createSync(recursive: true);
-          return AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
         },
         AndroidStudio: () => FakeAndroidStudio(),
       },
@@ -321,11 +312,7 @@
               .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
               .childFile(globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager')
               .createSync(recursive: true);
-          return AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
         },
         AndroidStudio: () => FakeAndroidStudio(),
       },
@@ -416,11 +403,7 @@
               .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
               .childFile(globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager')
               .createSync(recursive: true);
-          return AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
         },
         AndroidStudio: () => FakeAndroidStudio(),
       },
@@ -507,11 +490,7 @@
               .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
               .childFile(globals.platform.isWindows ? 'sdkmanager.bat' : 'sdkmanager')
               .createSync(recursive: true);
-          return AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
         },
         AndroidStudio: () => FakeAndroidStudio(),
       },
@@ -1486,11 +1465,7 @@
                 .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
                 .childFile(apkAnalyzerBinaryName)
                 .createSync(recursive: true);
-            return AndroidSdk(
-              fileSystem.directory(sdkPath()),
-              java: FakeJava(),
-              fileSystem: fileSystem,
-            );
+            return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
           },
           AndroidStudio: () => FakeAndroidStudio(),
           ProcessManager: () => processManager,
@@ -1557,11 +1532,7 @@
                 .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
                 .childFile(apkAnalyzerBinaryName)
                 .createSync(recursive: true);
-            return AndroidSdk(
-              fileSystem.directory(sdkPath()),
-              java: FakeJava(),
-              fileSystem: fileSystem,
-            );
+            return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
           },
           AndroidStudio: () => FakeAndroidStudio(),
           ProcessManager: () => processManager,
@@ -1621,11 +1592,7 @@
                 .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
                 .childFile(apkAnalyzerBinaryName)
                 .createSync(recursive: true);
-            return AndroidSdk(
-              fileSystem.directory(sdkPath()),
-              java: FakeJava(),
-              fileSystem: fileSystem,
-            );
+            return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
           },
           AndroidStudio: () => FakeAndroidStudio(),
         },
@@ -1639,11 +1606,7 @@
               .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
               .childFile(apkAnalyzerBinaryName)
               .createSync(recursive: true);
-          final sdk = AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          final sdk = AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
           final AndroidGradleBuilder builder = createBuilder(
             java: FakeJava(),
             logger: logger,
@@ -1709,11 +1672,7 @@
                 .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
                 .childFile(apkAnalyzerBinaryName)
                 .createSync(recursive: true);
-            return AndroidSdk(
-              fileSystem.directory(sdkPath()),
-              java: FakeJava(),
-              fileSystem: fileSystem,
-            );
+            return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
           },
           AndroidStudio: () => FakeAndroidStudio(),
         },
@@ -1727,11 +1686,7 @@
               .directory(fileSystem.path.join(sdkPath(), 'cmdline-tools', 'latest', 'bin'))
               .childFile(apkAnalyzerBinaryName)
               .createSync(recursive: true);
-          final sdk = AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          final sdk = AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
           final AndroidGradleBuilder builder = createBuilder(
             java: FakeJava(),
             logger: logger,
@@ -2223,11 +2178,7 @@
               .childFile('source.properties')
               .createSync(recursive: true);
           fileSystem.directory(ndkPath('29.0.13846066-bad')).createSync(recursive: true);
-          return AndroidSdk(
-            fileSystem.directory(sdkPath()),
-            java: FakeJava(),
-            fileSystem: fileSystem,
-          );
+          return AndroidSdk(fileSystem.directory(sdkPath()), java: FakeJava());
         },
         AndroidStudio: () => FakeAndroidStudio(),
       },
@@ -3293,7 +3244,6 @@
           return AndroidSdk(
             fileSystem.directory(sdkPath()),
             java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')),
-            fileSystem: fileSystem,
           );
         },
         AndroidStudio: () => FakeAndroidStudio(),
@@ -3397,7 +3347,6 @@
           return AndroidSdk(
             fileSystem.directory(sdkPath()),
             java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')),
-            fileSystem: fileSystem,
           );
         },
         AndroidStudio: () => FakeAndroidStudio(),
@@ -3504,7 +3453,6 @@
           return AndroidSdk(
             fileSystem.directory(sdkPath()),
             java: FakeJava(version: const Version.withText(21, 0, 0, '21.0.0')),
-            fileSystem: fileSystem,
           );
         },
         AndroidStudio: () => FakeAndroidStudio(),
diff --git a/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart b/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart
index 01f8df9..01dfece 100644
--- a/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart
+++ b/packages/flutter_tools/test/general.shard/android/android_sdk_test.dart
@@ -36,15 +36,16 @@
         config.setValue('android-sdk', sdkDir.path);
 
         try {
-          AndroidSdk.locateAndroidSdk()!;
+          final AndroidSdk sdk = AndroidSdk.locateAndroidSdk()!;
+          sdk.latestVersion;
         } on StateError catch (err) {
           fail('sdk.reinitialize() threw a StateError:\n$err');
         }
       },
       overrides: <Type, Generator>{
+        Config: () => config,
         FileSystem: () => fileSystem,
         ProcessManager: () => FakeProcessManager.any(),
-        Config: () => config,
       },
     );
 
@@ -510,6 +511,79 @@
         Config: () => config,
       },
     );
+
+    testUsingContext(
+      'does not initialize sdkVersions or latestVersion during constructor instantiation',
+      () {
+        final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
+        final sdk = AndroidSdk(sdkDir);
+
+        // Constructor did not scan build-tools or platforms.
+        // We verify by modifying the directory before first access.
+        fileSystem
+            .directory(fileSystem.path.join(sdkDir.path, 'platforms', 'android-22'))
+            .deleteSync(recursive: true);
+
+        // First access to latestVersion triggers initialization and sees only remaining platforms.
+        expect(sdk.latestVersion, isNotNull);
+        expect(sdk.latestVersion!.sdkLevel, 23);
+        expect(sdk.sdkVersions.length, 1);
+      },
+      overrides: <Type, Generator>{
+        Config: () => config,
+        FileSystem: () => fileSystem,
+        ProcessManager: () => FakeProcessManager.any(),
+      },
+    );
+
+    testUsingContext(
+      'evaluates sdkVersions and latestVersion lazily on first access',
+      () {
+        final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
+
+        // Accessing latestVersion triggers initialization.
+        final sdk1 = AndroidSdk(sdkDir);
+        expect(sdk1.latestVersion, isNotNull);
+        expect(sdk1.latestVersion!.sdkLevel, 23);
+        expect(sdk1.sdkVersions.length, 2);
+
+        // Accessing sdkVersions triggers initialization independently.
+        final sdk2 = AndroidSdk(sdkDir);
+        expect(sdk2.sdkVersions.length, 2);
+        expect(sdk2.latestVersion, isNotNull);
+        expect(sdk2.latestVersion!.sdkLevel, 23);
+      },
+      overrides: <Type, Generator>{
+        Config: () => config,
+        FileSystem: () => fileSystem,
+        ProcessManager: () => FakeProcessManager.any(),
+      },
+    );
+
+    testUsingContext(
+      'reinitialize updates sdkVersions and latestVersion when new platforms are installed',
+      () {
+        final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem);
+        final sdk = AndroidSdk(sdkDir);
+
+        expect(sdk.latestVersion!.sdkLevel, 23);
+
+        // Add android-34 platform.
+        fileSystem
+            .directory(fileSystem.path.join(sdkDir.path, 'platforms', 'android-34'))
+            .createSync(recursive: true);
+
+        sdk.reinitialize();
+
+        expect(sdk.latestVersion!.sdkLevel, 34);
+        expect(sdk.sdkVersions.length, 3);
+      },
+      overrides: <Type, Generator>{
+        Config: () => config,
+        FileSystem: () => fileSystem,
+        ProcessManager: () => FakeProcessManager.any(),
+      },
+    );
   });
 
   const llvmHostDirectoryName = <String, String>{
@@ -533,7 +607,7 @@
       final Directory sdkDir = createSdkDirectory(fileSystem: fileSystem, platform: platform);
       config.setValue('android-sdk', sdkDir.path);
 
-      final sdk = AndroidSdk(sdkDir, fileSystem: fileSystem);
+      final sdk = AndroidSdk(sdkDir);
       late File clang;
       late File ar;
       late File ld;
@@ -584,7 +658,7 @@
         final File ar = binDir.childFile('llvm-ar$extension')..createSync();
         final File ld = binDir.childFile('ld.lld$extension')..createSync();
 
-        final sdk = AndroidSdk(sdkDir, fileSystem: fileSystem);
+        final sdk = AndroidSdk(sdkDir);
         expect(sdk.getNdkClangPath(platform: platform, config: config), clang.path);
         expect(sdk.getNdkArPath(platform: platform, config: config), ar.path);
         expect(sdk.getNdkLdPath(platform: platform, config: config), ld.path);
@@ -612,7 +686,7 @@
       final File ar = binDir.childFile('llvm-ar$extension')..createSync();
       final File ld = binDir.childFile('ld.lld$extension')..createSync();
 
-      final sdk = AndroidSdk(sdkDir, fileSystem: fileSystem);
+      final sdk = AndroidSdk(sdkDir);
       expect(sdk.getNdkClangPath(platform: platform, config: config), clang.path);
       expect(sdk.getNdkArPath(platform: platform, config: config), ar.path);
       expect(sdk.getNdkLdPath(platform: platform, config: config), ld.path);
@@ -643,7 +717,7 @@
         final File ar = binDir.childFile('llvm-ar$extension')..createSync();
         final File ld = binDir.childFile('ld.lld$extension')..createSync();
 
-        final sdk = AndroidSdk(sdkDir, fileSystem: fileSystem);
+        final sdk = AndroidSdk(sdkDir);
         expect(sdk.getNdkClangPath(platform: platform, config: config), clang.path);
         expect(sdk.getNdkArPath(platform: platform, config: config), ar.path);
         expect(sdk.getNdkLdPath(platform: platform, config: config), ld.path);
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index d1f7196..10e79a4 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -2099,7 +2099,7 @@
   bool reinitialized = false;
 
   @override
-  void reinitialize({FileSystem? fileSystem}) {
+  void reinitialize() {
     reinitialized = true;
   }
 }