// 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:archive/archive.dart';
import 'package:file/memory.dart';
import 'package:file_testing/file_testing.dart';
import 'package:flutter_tools/src/android/android_sdk.dart';
import 'package:flutter_tools/src/android/android_studio.dart';
import 'package:flutter_tools/src/android/application_package.dart';
import 'package:flutter_tools/src/android/gradle.dart';
import 'package:flutter_tools/src/android/gradle_errors.dart';
import 'package:flutter_tools/src/android/gradle_utils.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/base/process.dart';
import 'package:flutter_tools/src/base/user_messages.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:flutter_tools/src/reporting/reporting.dart';
import 'package:test/fake.dart';

import '../../src/common.dart';
import '../../src/context.dart';
import '../../src/fake_process_manager.dart';
import '../../src/fakes.dart';

void main() {
  group('gradle build', () {
    late BufferLogger logger;
    late TestUsage testUsage;
    late FileSystem fileSystem;
    late FakeProcessManager processManager;

    setUp(() {
      processManager = FakeProcessManager.empty();
      logger = BufferLogger.test();
      testUsage = TestUsage();
      fileSystem = MemoryFileSystem.test();
      Cache.flutterRoot = '';
    });

    testUsingContext('Can immediately tool exit on recognized exit code/stderr', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
         'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
        exitCode: 1,
        stderr: '\nSome gradle message\n',
      ));

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      bool handlerCalled = false;
      await expectLater(() async {
       await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: <GradleHandledError>[
            GradleHandledError(
              test: (String line) {
                return line.contains('Some gradle message');
              },
              handler: ({
                String? line,
                FlutterProject? project,
                bool? usesAndroidX,
                bool? multidexEnabled
              }) async {
                handlerCalled = true;
                return GradleBuildStatus.exit;
              },
              eventLabel: 'random-event-label',
            ),
          ],
        );
      },
      throwsToolExit(
        message: 'Gradle task assembleRelease failed with exit code 1'
      ));

      expect(handlerCalled, isTrue);

      expect(testUsage.events, contains(
        const TestUsageEvent(
          'build',
          'gradle',
          label: 'gradle-random-event-label-failure',
          parameters: CustomDimensions(),
        ),
      ));
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('Verbose mode for APKs includes Gradle stacktrace and sets debug log level', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: BufferLogger.test(verbose: true),
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
         'gradlew',
          '--full-stacktrace',
          '--info',
          '-Pverbose=true',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      fileSystem.directory('build')
        .childDirectory('app')
        .childDirectory('outputs')
        .childDirectory('flutter-apk')
        .childFile('app-release.apk')
        .createSync(recursive: true);

      await builder.buildGradleApp(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        androidBuildInfo: const AndroidBuildInfo(
          BuildInfo(
            BuildMode.release,
            null,
            treeShakeIcons: false,
          ),
        ),
        target: 'lib/main.dart',
        isBuildingBundle: false,
        configOnly: false,
        localGradleErrors: <GradleHandledError>[],
      );
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('Can retry build on recognized exit code/stderr', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );

      const FakeCommand fakeCmd = FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
        exitCode: 1,
        stderr: '\nSome gradle message\n',
      );

      processManager.addCommand(fakeCmd);

      const int maxRetries = 2;
      for (int i = 0; i < maxRetries; i++) {
        processManager.addCommand(fakeCmd);
      }

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      int testFnCalled = 0;
      await expectLater(() async {
       await builder.buildGradleApp(
          maxRetries: maxRetries,
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: <GradleHandledError>[
            GradleHandledError(
              test: (String line) {
                if (line.contains('Some gradle message')) {
                  testFnCalled++;
                  return true;
                }
                return false;
              },
              handler: ({
                String? line,
                FlutterProject? project,
                bool? usesAndroidX,
                bool? multidexEnabled
              }) async {
                return GradleBuildStatus.retry;
              },
              eventLabel: 'random-event-label',
            ),
          ],
        );
      }, throwsToolExit(
        message: 'Gradle task assembleRelease failed with exit code 1'
      ));

      expect(logger.statusText, contains('Retrying Gradle Build: #1, wait time: 100ms'));
      expect(logger.statusText, contains('Retrying Gradle Build: #2, wait time: 200ms'));

      expect(testFnCalled, equals(maxRetries + 1));
      expect(testUsage.events, contains(
        const TestUsageEvent(
          'build',
          'gradle',
          label: 'gradle-random-event-label-failure',
          parameters: CustomDimensions(),
        ),
      ));
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('Converts recognized ProcessExceptions into tools exits', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
        exitCode: 1,
        stderr: '\nSome gradle message\n',
      ));

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      bool handlerCalled = false;
      await expectLater(() async {
       await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: <GradleHandledError>[
            GradleHandledError(
              test: (String line) {
                return line.contains('Some gradle message');
              },
              handler: ({
                String? line,
                FlutterProject? project,
                bool? usesAndroidX,
                bool? multidexEnabled
              }) async {
                handlerCalled = true;
                return GradleBuildStatus.exit;
              },
              eventLabel: 'random-event-label',
            ),
          ],
        );
      },
      throwsToolExit(
        message: 'Gradle task assembleRelease failed with exit code 1'
      ));

      expect(handlerCalled, isTrue);

      expect(testUsage.events, contains(
        const TestUsageEvent(
          'build',
          'gradle',
          label: 'gradle-random-event-label-failure',
          parameters: CustomDimensions(),
        ),
      ));
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('rethrows unrecognized ProcessException', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(FakeCommand(
        command: const <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
        exitCode: 1,
        onRun: () {
          throw const ProcessException('', <String>[], 'Unrecognized');
        }
      ));

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      await expectLater(() async {
        await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: const <GradleHandledError>[],
        );
      }, throwsProcessException());
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('logs success event after a successful retry', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
        exitCode: 1,
        stderr: '\nnSome gradle message\n',
      ));
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      fileSystem.directory('build')
        .childDirectory('app')
        .childDirectory('outputs')
        .childDirectory('flutter-apk')
        .childFile('app-release.apk')
        .createSync(recursive: true);

      await builder.buildGradleApp(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        androidBuildInfo: const AndroidBuildInfo(
          BuildInfo(
            BuildMode.release,
            null,
            treeShakeIcons: false,
          ),
        ),
        target: 'lib/main.dart',
        isBuildingBundle: false,
        configOnly: false,
        localGradleErrors: <GradleHandledError>[
          GradleHandledError(
            test: (String line) {
              return line.contains('Some gradle message');
            },
            handler: ({
              String? line,
              FlutterProject? project,
              bool? usesAndroidX,
                bool? multidexEnabled
            }) async {
              return GradleBuildStatus.retry;
            },
            eventLabel: 'random-event-label',
          ),
        ],
      );
      expect(testUsage.events, contains(
        const TestUsageEvent(
          'build',
          'gradle',
          label: 'gradle-random-event-label-success',
          parameters: CustomDimensions(),
        ),
      ));
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('performs code size analysis and sends analytics', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(
          environment: <String, String>{
            'HOME': '/home',
          },
        ),
      );
       processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Pcode-size-directory=foo',
          'assembleRelease',
        ],
      ));

      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      final Archive archive = Archive()
        ..addFile(ArchiveFile('AndroidManifest.xml', 100,  List<int>.filled(100, 0)))
        ..addFile(ArchiveFile('META-INF/CERT.RSA', 10,  List<int>.filled(10, 0)))
        ..addFile(ArchiveFile('META-INF/CERT.SF', 10,  List<int>.filled(10, 0)))
        ..addFile(ArchiveFile('lib/arm64-v8a/libapp.so', 50,  List<int>.filled(50, 0)))
        ..addFile(ArchiveFile('lib/arm64-v8a/libflutter.so', 50, List<int>.filled(50, 0)));

      fileSystem.directory('build')
        .childDirectory('app')
        .childDirectory('outputs')
        .childDirectory('flutter-apk')
        .childFile('app-release.apk')
        ..createSync(recursive: true)
        ..writeAsBytesSync(ZipEncoder().encode(archive)!);

      fileSystem.file('foo/snapshot.arm64-v8a.json')
        ..createSync(recursive: true)
        ..writeAsStringSync(r'''
[
  {
    "l": "dart:_internal",
    "c": "SubListIterable",
    "n": "[Optimized] skip",
    "s": 2400
  }
]''');
      fileSystem.file('foo/trace.arm64-v8a.json')
        ..createSync(recursive: true)
        ..writeAsStringSync('{}');

      await builder.buildGradleApp(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        androidBuildInfo: const AndroidBuildInfo(
          BuildInfo(
            BuildMode.release,
            null,
            treeShakeIcons: false,
            codeSizeDirectory: 'foo',
          ),
          targetArchs: <AndroidArch>[AndroidArch.arm64_v8a],
        ),
        target: 'lib/main.dart',
        isBuildingBundle: false,
        configOnly: false,
        localGradleErrors: <GradleHandledError>[],
      );

      expect(testUsage.events, contains(
        const TestUsageEvent(
          'code-size-analysis',
          'apk',
        ),
      ));
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('indicates that an APK has been built successfully', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));
      fileSystem.directory('android')
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);

      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      fileSystem.directory('build')
        .childDirectory('app')
        .childDirectory('outputs')
        .childDirectory('flutter-apk')
        .childFile('app-release.apk')
        .createSync(recursive: true);

      await builder.buildGradleApp(
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        androidBuildInfo: const AndroidBuildInfo(
          BuildInfo(
            BuildMode.release,
            null,
            treeShakeIcons: false,
          ),
        ),
        target: 'lib/main.dart',
        isBuildingBundle: false,
        configOnly: false,
        localGradleErrors: const <GradleHandledError>[],
      );

      expect(
        logger.statusText,
        contains('Built build/app/outputs/flutter-apk/app-release.apk (0.0MB)'),
      );
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('Uses namespace attribute if manifest lacks a package attribute', () async {
      final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
      final AndroidSdk sdk = FakeAndroidSdk();

      fileSystem.directory(project.android.hostAppGradleRoot)
        .childFile('build.gradle')
        .createSync(recursive: true);

      fileSystem.directory(project.android.hostAppGradleRoot)
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync(
'''
apply from: irrelevant/flutter.gradle

android {
    namespace 'com.example.foo'
}
''');

      fileSystem.directory(project.android.hostAppGradleRoot)
        .childDirectory('app')
        .childDirectory('src')
        .childDirectory('main')
        .childFile('AndroidManifest.xml')
        ..createSync(recursive: true)
        ..writeAsStringSync(r'''
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="namespacetest"
        android:name="${applicationName}"
        android:icon="@mipmap/ic_launcher">
        <activity
            android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN"/>
                <category android:name="android.intent.category.LAUNCHER"/>
            </intent-filter>
        </activity>
    </application>
</manifest>
''');

      final AndroidApk? androidApk = await AndroidApk.fromAndroidProject(
        project.android,
        androidSdk: sdk,
        fileSystem: fileSystem,
        logger: logger,
        processManager: processManager,
        processUtils: ProcessUtils(processManager: processManager, logger: logger),
        userMessages: UserMessages(),
        buildInfo: const BuildInfo(BuildMode.debug, null, treeShakeIcons: false),
      );

      expect(androidApk?.id, 'com.example.foo');
    });

    testUsingContext("doesn't indicate how to consume an AAR when printHowToConsumeAar is false", () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
           'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=1.0',
          '-q',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          'assembleAarRelease',
        ],
      ));

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
        .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
        .createSync(recursive: true);
      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await builder.buildGradleAar(
        androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        outputDirectory: fileSystem.directory('build/'),
        target: '',
        buildNumber: '1.0',
      );

      expect(
        logger.statusText,
        contains('Built build/outputs/repo'),
      );
      expect(
        logger.statusText.contains('Consuming the Module'),
        isFalse,
      );
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('Verbose mode for AARs includes Gradle stacktrace and sets debug log level', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: BufferLogger.test(verbose: true),
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=1.0',
          '--full-stacktrace',
          '--info',
          '-Pverbose=true',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          'assembleAarRelease',
        ],
      ));

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
          .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await builder.buildGradleAar(
        androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        outputDirectory: fileSystem.directory('build/'),
        target: '',
        buildNumber: '1.0',
      );
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('gradle exit code and stderr is forwarded to tool exit', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=1.0',
          '-q',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          'assembleAarRelease',
        ],
        exitCode: 108,
        stderr: 'Gradle task assembleAarRelease failed with exit code 108.',
      ));

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
          .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await expectLater(() async =>
        builder.buildGradleAar(
          androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          outputDirectory: fileSystem.directory('build/'),
          target: '',
          buildNumber: '1.0',
        ), throwsToolExit(exitCode: 108, message: 'Gradle task assembleAarRelease failed with exit code 108.'));
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build apk uses selected local engine with arm32 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_arm'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_arm',
          '-Ptarget-platform=android-arm',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));

      fileSystem.file('out/android_arm/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      fileSystem.file('android/gradlew').createSync(recursive: true);
      fileSystem.directory('android')
        .childFile('gradle.properties')
        .createSync(recursive: true);
      fileSystem.file('android/build.gradle')
        .createSync(recursive: true);
      fileSystem.directory('android')
        .childDirectory('app')
        .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      await expectLater(() async {
        await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: const <GradleHandledError>[],
        );
      }, throwsToolExit());
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build apk uses selected local engine with arm64 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_arm64'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_arm64',
          '-Ptarget-platform=android-arm64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));

      fileSystem.file('out/android_arm64/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      fileSystem.file('android/gradlew').createSync(recursive: true);
      fileSystem.directory('android')
          .childFile('gradle.properties')
          .createSync(recursive: true);
      fileSystem.file('android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('android')
          .childDirectory('app')
          .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      await expectLater(() async {
        await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: const <GradleHandledError>[],
        );
      }, throwsToolExit());
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build apk uses selected local engine with x86 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_x86'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_x86',
          '-Ptarget-platform=android-x86',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));

      fileSystem.file('out/android_x86/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      fileSystem.file('android/gradlew').createSync(recursive: true);
      fileSystem.directory('android')
          .childFile('gradle.properties')
          .createSync(recursive: true);
      fileSystem.file('android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('android')
          .childDirectory('app')
          .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      await expectLater(() async {
        await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: const <GradleHandledError>[],
        );
      }, throwsToolExit());
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build apk uses selected local engine with x64 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_x64'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-q',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_x64',
          '-Ptarget-platform=android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
        exitCode: 1,
      ));

      fileSystem.file('out/android_x64/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      fileSystem.file('android/gradlew').createSync(recursive: true);
      fileSystem.directory('android')
          .childFile('gradle.properties')
          .createSync(recursive: true);
      fileSystem.file('android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('android')
          .childDirectory('app')
          .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      await expectLater(() async {
        await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: const <GradleHandledError>[],
        );
      }, throwsToolExit());
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('honors --no-android-gradle-daemon setting', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(
        const FakeCommand(command: <String>[
          'gradlew',
          '-q',
          '--no-daemon',
          '-Ptarget-platform=android-arm,android-arm64,android-x64',
          '-Ptarget=lib/main.dart',
          '-Pbase-application-name=io.flutter.app.FlutterApplication',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          'assembleRelease',
        ],
      ));
      fileSystem.file('android/gradlew').createSync(recursive: true);

      fileSystem.directory('android')
          .childFile('gradle.properties')
          .createSync(recursive: true);
      fileSystem.file('android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('android')
          .childDirectory('app')
          .childFile('build.gradle')
        ..createSync(recursive: true)
        ..writeAsStringSync('apply from: irrelevant/flutter.gradle');

      await expectLater(() async {
        await builder.buildGradleApp(
          project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
          androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(
              BuildMode.release,
              null,
              treeShakeIcons: false,
              androidGradleDaemon: false,
            ),
          ),
          target: 'lib/main.dart',
          isBuildingBundle: false,
          configOnly: false,
          localGradleErrors: const <GradleHandledError>[],
        );
      }, throwsToolExit());
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build aar uses selected local engine with arm32 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_arm'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=2.0',
          '-q',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_arm',
          '-Ptarget-platform=android-arm',
          'assembleAarRelease',
        ],
      ));

      fileSystem.file('out/android_arm/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_arm/armeabi_v7a_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm/armeabi_v7a_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm/armeabi_v7a_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_arm/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.directory('.android/gradle')
        .createSync(recursive: true);
      fileSystem.directory('.android/gradle/wrapper')
        .createSync(recursive: true);
      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
        .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
        .createSync(recursive: true);

      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await builder.buildGradleAar(
        androidBuildInfo: const AndroidBuildInfo(BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        outputDirectory: fileSystem.directory('build/'),
        target: '',
        buildNumber: '2.0',
      );

      expect(fileSystem.link(
        'build/outputs/repo/io/flutter/flutter_embedding_release/'
        '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
        'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
      ), exists);
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build aar uses selected local engine with x64 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_arm64'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=2.0',
          '-q',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_arm64',
          '-Ptarget-platform=android-arm64',
          'assembleAarRelease',
        ],
      ));

      fileSystem.file('out/android_arm64/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_arm64/arm64_v8a_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm64/arm64_v8a_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm64/arm64_v8a_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_arm64/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_arm64/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_arm64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.directory('.android/gradle')
          .createSync(recursive: true);
      fileSystem.directory('.android/gradle/wrapper')
          .createSync(recursive: true);
      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
          .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await builder.buildGradleAar(
        androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        outputDirectory: fileSystem.directory('build/'),
        target: '',
        buildNumber: '2.0',
      );

      expect(fileSystem.link(
        'build/outputs/repo/io/flutter/flutter_embedding_release/'
        '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
        'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
      ), exists);
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build aar uses selected local engine with x86 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_x86'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
          'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=2.0',
          '-q',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_x86',
          '-Ptarget-platform=android-x86',
          'assembleAarRelease',
        ],
      ));

      fileSystem.file('out/android_x86/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_x86/x86_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x86/x86_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x86/x86_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_x86/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x86/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x86/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.directory('.android/gradle')
          .createSync(recursive: true);
      fileSystem.directory('.android/gradle/wrapper')
          .createSync(recursive: true);
      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
          .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await builder.buildGradleAar(
        androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        outputDirectory: fileSystem.directory('build/'),
        target: '',
        buildNumber: '2.0',
      );

      expect(fileSystem.link(
        'build/outputs/repo/io/flutter/flutter_embedding_release/'
        '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
        'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
      ), exists);
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });

    testUsingContext('build aar uses selected local engine on x64 ABI', () async {
      final AndroidGradleBuilder builder = AndroidGradleBuilder(
        logger: logger,
        processManager: processManager,
        fileSystem: fileSystem,
        artifacts: Artifacts.test(localEngine: 'out/android_x64'),
        usage: testUsage,
        gradleUtils: FakeGradleUtils(),
        platform: FakePlatform(),
      );
      processManager.addCommand(const FakeCommand(
        command: <String>[
         'gradlew',
          '-I=/packages/flutter_tools/gradle/aar_init_script.gradle',
          '-Pflutter-root=/',
          '-Poutput-dir=build/',
          '-Pis-plugin=false',
          '-PbuildNumber=2.0',
          '-q',
          '-Pdart-obfuscation=false',
          '-Ptrack-widget-creation=false',
          '-Ptree-shake-icons=false',
          '-Plocal-engine-repo=/.tmp_rand0/flutter_tool_local_engine_repo.rand0',
          '-Plocal-engine-build-mode=release',
          '-Plocal-engine-out=out/android_x64',
          '-Ptarget-platform=android-x64',
          'assembleAarRelease',
        ],
      ));

      fileSystem.file('out/android_x64/flutter_embedding_release.pom')
        ..createSync(recursive: true)
        ..writeAsStringSync('''
<?xml version="1.0" encoding="UTF-8"?>
<project>
  <version>1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b</version>
  <dependencies>
  </dependencies>
</project>
''');
      fileSystem.file('out/android_x64/x86_64_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x64/x86_64_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x64/x86_64_release.maven-metadata.xml').createSync(recursive: true);
      fileSystem.file('out/android_x64/flutter_embedding_release.jar').createSync(recursive: true);
      fileSystem.file('out/android_x64/flutter_embedding_release.pom').createSync(recursive: true);
      fileSystem.file('out/android_x64/flutter_embedding_release.maven-metadata.xml').createSync(recursive: true);

      final File manifestFile = fileSystem.file('pubspec.yaml');
      manifestFile.createSync(recursive: true);
      manifestFile.writeAsStringSync('''
        flutter:
          module:
            androidPackage: com.example.test
        '''
      );

      fileSystem.directory('.android/gradle')
          .createSync(recursive: true);
      fileSystem.directory('.android/gradle/wrapper')
          .createSync(recursive: true);
      fileSystem.file('.android/gradlew').createSync(recursive: true);
      fileSystem.file('.android/gradle.properties')
          .writeAsStringSync('irrelevant');
      fileSystem.file('.android/build.gradle')
          .createSync(recursive: true);
      fileSystem.directory('build/outputs/repo').createSync(recursive: true);

      await builder.buildGradleAar(
        androidBuildInfo: const AndroidBuildInfo(
            BuildInfo(BuildMode.release, null, treeShakeIcons: false)),
        project: FlutterProject.fromDirectoryTest(fileSystem.currentDirectory),
        outputDirectory: fileSystem.directory('build/'),
        target: '',
        buildNumber: '2.0',
      );

      expect(fileSystem.link(
        'build/outputs/repo/io/flutter/flutter_embedding_release/'
        '1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b/'
        'flutter_embedding_release-1.0.0-73fd6b049a80bcea2db1f26c7cee434907cd188b.pom'
      ), exists);
      expect(processManager, hasNoRemainingExpectations);
    }, overrides: <Type, Generator>{
      AndroidStudio: () => FakeAndroidStudio(),
    });
  });
}

class FakeGradleUtils extends Fake implements GradleUtils {
  @override
  String getExecutable(FlutterProject project) {
    return 'gradlew';
  }
}

class FakeAndroidStudio extends Fake implements AndroidStudio {
  @override
  String get javaPath => 'java';
}
