Enforce a policy on supported Gradle, Java, AGP, and KGP versions (#142000)

Policy per https://flutter.dev/go/android-dependency-versions.

https://github.com/flutter/flutter/issues/140913

~Still a WIP while I clean up some error handling, remove some prints, and figure out a Java test (more difficult than the others because I believe we can only install one java version per ci shard).~

~Also it looks like there are errors that I need to fix when this checking is applied to a project that uses the old way of applying AGP/KGP using the top-level `build.gradle` file (instead of the new template way of applying them in the `settings.gradle` file).~ Done, this is why [these lines exist](https://github.com/flutter/flutter/blob/9af6bae6b9a8d7a8a363467b834f52bfc64c9336/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy#L72-L88) in `flutter.groovy`. They just needed to be added
diff --git a/packages/flutter_tools/gradle/build.gradle.kts b/packages/flutter_tools/gradle/build.gradle.kts
index e14f1c3..c2ce5ff 100644
--- a/packages/flutter_tools/gradle/build.gradle.kts
+++ b/packages/flutter_tools/gradle/build.gradle.kts
@@ -28,8 +28,9 @@
 
 dependencies {
     // When bumping, also update:
-    //  * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/flutter.groovy
+    //  * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+    //  * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+    //  * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
     //  * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart
-    //  * AGP version in buildscript block in packages/flutter_tools/gradle/src/main/flutter.groovy
     compileOnly("com.android.tools.build:gradle:7.3.0")
 }
diff --git a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
index 1546c1d..5d3ad4e 100644
--- a/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+++ b/packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
@@ -113,7 +113,8 @@
     }
     dependencies {
         // When bumping, also update:
-        //  * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/flutter.groovy
+        //  * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+        //  * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
         //  * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart
         //  * AGP version in dependencies block in packages/flutter_tools/gradle/build.gradle.kts
         classpath("com.android.tools.build:gradle:7.3.0")
@@ -321,6 +322,23 @@
         String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
         flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile()
 
+        // Validate that the provided Gradle, Java, AGP, and KGP versions are all within our
+        // supported range.
+        final Boolean shouldSkipDependencyChecks = project.hasProperty("skipDependencyChecks")
+                && project.getProperty("skipDependencyChecks");
+        if (!shouldSkipDependencyChecks) {
+            try {
+                final String dependencyCheckerPluginPath = Paths.get(flutterRoot.absolutePath,
+                        "packages", "flutter_tools", "gradle", "src", "main", "kotlin",
+                        "dependency_version_checker.gradle.kts")
+                project.apply from: dependencyCheckerPluginPath
+            } catch (Exception ignored) {
+                project.logger.error("Warning: Flutter was unable to detect project Gradle, Java, " +
+                        "AGP, and KGP versions. Skipping dependency version checking. Error was: "
+                        + ignored)
+            }
+        }
+
         // Use Kotlin DSL to handle baseApplicationName logic due to Groovy dynamic dispatch bug.
         project.apply from: Paths.get(flutterRoot.absolutePath, "packages", "flutter_tools", "gradle", "src", "main", "kotlin", "flutter.gradle.kts")
 
@@ -1516,6 +1534,8 @@
     Boolean validateDeferredComponents
 
     @Optional @Input
+    Boolean skipDependencyChecks
+    @Optional @Input
     String flavor
 
     @OutputFiles
diff --git a/packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts b/packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
new file mode 100644
index 0000000..df14277
--- /dev/null
+++ b/packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts
@@ -0,0 +1,330 @@
+// 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 org.gradle.api.JavaVersion
+import org.jetbrains.kotlin.gradle.plugin.KotlinAndroidPluginWrapper
+
+// This buildscript block supplies dependencies for this file's own import
+// declarations above. It exists solely for compatibility with projects that
+// have not migrated to declaratively apply the Flutter Gradle Plugin;
+// for those that have, FGP's `build.gradle.kts`  takes care of this.
+buildscript {
+    repositories {
+        google()
+        mavenCentral()
+    }
+    dependencies {
+        // When bumping, also update:
+        //  * ndkVersion in FlutterExtension in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+        //  * AGP version in the buildscript block in packages/flutter_tools/gradle/src/main/groovy/flutter.groovy
+        //  * AGP version constants in packages/flutter_tools/lib/src/android/gradle_utils.dart
+        //  * AGP version in dependencies block in packages/flutter_tools/gradle/build.gradle.kts
+        classpath("com.android.tools.build:gradle:7.3.0")
+        classpath("org.jetbrains.kotlin:kotlin-gradle-plugin:1.7.10")
+    }
+}
+
+apply<FlutterDependencyCheckerPlugin>()
+
+class FlutterDependencyCheckerPlugin : Plugin<Project> {
+    override fun apply(project: Project) {
+        DependencyVersionChecker.checkDependencyVersions(project)
+    }
+}
+
+
+class DependencyVersionChecker {
+    companion object {
+        private const val GRADLE_NAME : String = "Gradle"
+        private const val JAVA_NAME : String = "Java"
+        private const val AGP_NAME : String = "Android Gradle Plugin"
+        private const val KGP_NAME : String = "Kotlin"
+
+        // The following messages represent best effort guesses at where a Flutter developer should
+        // look to upgrade a dependency that is below the corresponding threshold. Developers can
+        // change some of these locations, so they are not guaranteed to be accurate.
+        private fun getPotentialGradleFix(projectDirectory : String) : String {
+            return "Your project's gradle version is typically " +
+                    "defined in the gradle wrapper file. By default, this can be found at " +
+                    "$projectDirectory/gradle/wrapper/gradle-wrapper.properties. \n" +
+                    "For more information, see https://docs.gradle.org/current/userguide/gradle_wrapper.html."
+        }
+
+        // The potential java fix does not make use of the project directory,
+        // so it left as a constant.
+        private const val POTENTIAL_JAVA_FIX : String = "The Java version used by Flutter can be " +
+                "set with `flutter config --jdk-dir=<path>`. \nFor more information about how Flutter " +
+                "chooses which version of Java to use, see the --jdk-dir section of the " +
+                "output of `flutter config -h`."
+
+        private fun getPotentialAGPFix(projectDirectory : String) : String {
+            return "Your project's AGP version is typically " +
+                    "defined the plugins block of the `settings.gradle` file " +
+                    "($projectDirectory/settings.gradle), by a plugin with the id of " +
+                    "com.android.application. \nIf you don't see a plugins block, your project " +
+                    "was likely created with an older template version. In this case it is most " +
+                    "likely defined in the top-level build.gradle file " +
+                    "($projectDirectory/build.gradle) by the following line in the dependencies" +
+                    " block of the buildscript: \"classpath 'com.android.tools.build:gradle:<version>'\"."
+        }
+
+        private fun getPotentialKGPFix(projectDirectory : String) : String {
+            return "Your project's KGP version is typically " +
+                    "defined the plugins block of the `settings.gradle` file " +
+                    "($projectDirectory/settings.gradle), by a plugin with the id of " +
+                    "org.jetbrains.kotlin.android. \nIf you don't see a plugins block, your project " +
+                    "was likely created with an older template version, in which case it is most " +
+                    "likely defined in the top-level build.gradle file " +
+                    "($projectDirectory/build.gradle) by the ext.kotlin_version property."
+        }
+
+        // The following versions define our support policy for Gradle, Java, AGP, and KGP.
+        // All "error" versions are currently set to 0 as this policy is new. They will be increased
+        // to match the current values of the "warn" versions in the next release.
+        // Before updating any "error" version, ensure that you have updated the corresponding
+        // "warn" version for a full release to provide advanced warning. See
+        // flutter.dev/go/android-dependency-versions for more.
+        // TODO(gmackall): https://github.com/flutter/flutter/issues/142653.
+        val warnGradleVersion : Version = Version(7,0,2)
+        val errorGradleVersion : Version = Version(0,0,0)
+
+        val warnJavaVersion : JavaVersion = JavaVersion.VERSION_11
+        val errorJavaVersion : JavaVersion = JavaVersion.VERSION_1_1
+
+        val warnAGPVersion : Version = Version(7,0,0)
+        val errorAGPVersion : Version = Version(0,0,0)
+
+        val warnKGPVersion : Version = Version(1,5,0)
+        val errorKGPVersion : Version = Version(0,0,0)
+
+        /**
+         * Checks if the project's Android build time dependencies are each within the respective
+         * version range that we support. When we can't find a version for a given dependency
+         * we treat it as within the range for the purpose of this check.
+         */
+        fun checkDependencyVersions(project : Project) {
+            var agpVersion : Version? = null
+            var kgpVersion : Version? = null
+
+            checkGradleVersion(getGradleVersion(project), project)
+            checkJavaVersion(getJavaVersion(project), project)
+            agpVersion = getAGPVersion(project)
+            if (agpVersion != null) {
+                checkAGPVersion(agpVersion, project)
+            } else {
+                project.logger.error("Warning: unable to detect project AGP version. Skipping " +
+                        "version checking. ")
+            }
+
+            kgpVersion = getKGPVersion(project)
+            if (kgpVersion != null) {
+                checkKGPVersion(kgpVersion, project)
+            } else {
+                project.logger.error("Warning: unable to detect project KGP version. Skipping " +
+                        "version checking.")
+            }
+        }
+
+        // https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api.invocation/-gradle/index.html#-837060600%2FFunctions%2F-1793262594
+        fun getGradleVersion(project : Project) : Version {
+            val untrimmedGradleVersion : String = project.gradle.getGradleVersion()
+            // Trim to handle candidate gradle versions (example 7.6-rc-4). This means we treat all
+            // candidate versions of gradle as the same as their base version
+            // (i.e., "7.6"="7.6-rc-4").
+            return Version.fromString(untrimmedGradleVersion.substringBefore('-'))
+        }
+
+        // https://docs.gradle.org/current/kotlin-dsl/gradle/org.gradle.api/-java-version/index.html#-1790786897%2FFunctions%2F-1793262594
+        fun getJavaVersion(project : Project) : JavaVersion {
+            return JavaVersion.current()
+        }
+
+        // This approach is taken from AGP's own version checking plugin:
+        // https://android.googlesource.com/platform/tools/base/+/1839aa23b8dc562005e2f0f0cc8e8b4c5caa37d0/build-system/gradle-core/src/main/java/com/android/build/gradle/internal/utils/agpVersionChecker.kt#58.
+        fun getAGPVersion(project: Project): Version? {
+            val agpPluginName : String = "com.android.base";
+            val agpVersionFieldName : String = "ANDROID_GRADLE_PLUGIN_VERSION"
+            var agpVersion: Version? = null
+            try {
+                agpVersion = Version.fromString(
+                    project.plugins.getPlugin(agpPluginName)::class.java.classLoader.loadClass(
+                        com.android.Version::class.java.name
+                    ).fields.find { it.name == agpVersionFieldName }!!
+                        .get(null) as String
+                )
+            } catch (ignored: ClassNotFoundException) {
+                // Use deprecated Version class as it exists in older AGP (com.android.Version) does
+                // not exist in those versions.
+                agpVersion = Version.fromString(
+                    project.plugins.getPlugin(agpPluginName)::class.java.classLoader.loadClass(
+                        com.android.builder.model.Version::class.java.name
+                    ).fields.find { it.name == agpVersionFieldName }!!
+                        .get(null) as String
+                )
+            }
+            return agpVersion
+        }
+
+        fun getKGPVersion(project : Project) : Version? {
+            val kotlinVersionProperty : String = "kotlin_version"
+            val firstKotlinVersionFieldName : String = "pluginVersion"
+            val secondKotlinVersionFieldName : String = "kotlinPluginVersion"
+            // This property corresponds to application of the Kotlin Gradle plugin in the
+            // top-level build.gradle file.
+            if (project.hasProperty(kotlinVersionProperty)) {
+                return Version.fromString(project.properties.get(kotlinVersionProperty) as String)
+            }
+            val kotlinPlugin = project.getPlugins()
+                .findPlugin(KotlinAndroidPluginWrapper::class.java)
+            val versionfield =
+                kotlinPlugin?.javaClass?.kotlin?.members?.first { it.name == firstKotlinVersionFieldName || it.name == secondKotlinVersionFieldName }
+            val versionString = versionfield?.call(kotlinPlugin)
+            if (versionString == null) {
+                return null
+            } else {
+                return Version.fromString(versionString!! as String)
+            }
+        }
+
+        private fun getErrorMessage(dependencyName : String,
+                                    versionString : String,
+                                    errorVersion : String,
+                                    potentialFix : String) : String {
+            return "Error: Your project's $dependencyName version ($versionString) is lower " +
+                    "than Flutter's minimum supported version of $errorVersion. Please upgrade " +
+                    "your $dependencyName version. \nAlternatively, use the flag " +
+                    "\"--android-skip-build-dependency-validation\" to bypass this check.\n\n" +
+                    "Potential fix: $potentialFix"
+        }
+
+        private fun getWarnMessage(dependencyName : String,
+                                   versionString : String,
+                                   warnVersion : String,
+                                   potentialFix : String) : String {
+            return "Warning: Flutter support for your project's $dependencyName version " +
+                    "($versionString) will soon be dropped. Please upgrade your $dependencyName " +
+                    "version to a version of at least $warnVersion soon." +
+                    "\nAlternatively, use the flag \"--android-skip-build-dependency-validation\"" +
+                    " to bypass this check.\n\n Potential fix: $potentialFix"
+        }
+
+        fun checkGradleVersion(version : Version, project : Project) {
+            if (version < errorGradleVersion) {
+                val errorMessage : String = getErrorMessage(
+                    GRADLE_NAME,
+                    version.toString(),
+                    errorGradleVersion.toString(),
+                    getPotentialGradleFix(project.getRootDir().getPath())
+                )
+                throw GradleException(errorMessage)
+            }
+            else if (version < warnGradleVersion) {
+                val warnMessage : String = getWarnMessage(
+                    GRADLE_NAME,
+                    version.toString(),
+                    warnGradleVersion.toString(),
+                    getPotentialGradleFix(project.getRootDir().getPath())
+                )
+                project.logger.error(warnMessage)
+            }
+        }
+
+        fun checkJavaVersion(version : JavaVersion, project : Project) {
+            if (version < errorJavaVersion) {
+                val errorMessage : String = getErrorMessage(
+                    JAVA_NAME,
+                    version.toString(),
+                    errorJavaVersion.toString(),
+                    POTENTIAL_JAVA_FIX
+                )
+                throw GradleException(errorMessage)
+            }
+            else if (version < warnJavaVersion) {
+                val warnMessage : String = getWarnMessage(
+                    JAVA_NAME,
+                    version.toString(),
+                    warnJavaVersion.toString(),
+                    POTENTIAL_JAVA_FIX
+                )
+                project.logger.error(warnMessage)
+            }
+        }
+
+        fun checkAGPVersion(version : Version, project : Project) {
+            if (version < errorAGPVersion) {
+                val errorMessage : String = getErrorMessage(
+                    AGP_NAME,
+                    version.toString(),
+                    errorAGPVersion.toString(),
+                    getPotentialAGPFix(project.getRootDir().getPath())
+                )
+                throw GradleException(errorMessage)
+            }
+            else if (version < warnAGPVersion) {
+                val warnMessage : String = getWarnMessage(
+                    AGP_NAME,
+                    version.toString(),
+                    warnAGPVersion.toString(),
+                    getPotentialAGPFix(project.getRootDir().getPath())
+                )
+                project.logger.error(warnMessage)
+            }
+        }
+
+        fun checkKGPVersion(version : Version, project : Project) {
+            if (version < errorKGPVersion) {
+                val errorMessage : String = getErrorMessage(
+                    KGP_NAME,
+                    version.toString(),
+                    errorKGPVersion.toString(),
+                    getPotentialKGPFix(project.getRootDir().getPath())
+                )
+                throw GradleException(errorMessage)
+            }
+            else if (version < warnKGPVersion) {
+                val warnMessage : String = getWarnMessage(
+                    KGP_NAME,
+                    version.toString(),
+                    warnKGPVersion.toString(),
+                    getPotentialKGPFix(project.getRootDir().getPath())
+                )
+                project.logger.error(warnMessage)
+            }
+        }
+    }
+}
+
+
+// Helper class to parse the versions that are provided as plain strings (Gradle, Kotlin) and
+// perform easy comparisons. All versions will have a major, minor, and patch value. These values
+// default to 0 when they are not provided or are otherwise unparseable.
+// For example the version strings "8.2", "8.2.2hfd", and "8.2.0" would parse to the same version.
+class Version(val major : Int, val minor : Int, val patch : Int) : Comparable<Version> {
+    companion object {
+        fun fromString(version : String) : Version {
+            val asList : List<String> = version.split(".")
+            val convertedToNumbers : List<Int> = asList.map {it.toIntOrNull() ?: 0}
+            return Version(
+                major = convertedToNumbers.getOrElse(0, {0}),
+                minor = convertedToNumbers.getOrElse(1, {0}),
+                patch = convertedToNumbers.getOrElse(2, {0})
+            )
+        }
+    }
+    override fun compareTo(otherVersion : Version) : Int {
+        if (major != otherVersion.major) {
+            return major - otherVersion.major
+        }
+        if (minor != otherVersion.minor) {
+            return minor - otherVersion.minor
+        }
+        if (patch != otherVersion.patch) {
+            return patch - otherVersion.patch
+        }
+        return 0
+    }
+    override fun toString() : String {
+        return major.toString() + "." + minor.toString() + "." + patch.toString()
+    }
+}
diff --git a/packages/flutter_tools/lib/src/android/README.md b/packages/flutter_tools/lib/src/android/README.md
index a6b9580..a6845f4 100644
--- a/packages/flutter_tools/lib/src/android/README.md
+++ b/packages/flutter_tools/lib/src/android/README.md
@@ -29,6 +29,11 @@
 SDK versions are updated (you should see these fail if you do not fix them
 preemptively).
 
+Also, make sure to also update to the same version in the following places:
+- The version in the buildscript block in `packages/flutter_tools/gradle/src/main/groovy/flutter.groovy`.
+- The version in the buildscript block in `packages/flutter_tools/gradle/src/main/kotlin/dependency_version_checker.gradle.kts`.
+- The version in the dependencies block in `packages/flutter_tools/gradle/build.gradle.kts`.
+
 #### Gradle
 When updating the Gradle version used in project templates
 (`templateDefaultGradleVersion`), make sure that:
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 1b60f4e..12a92d4 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -370,6 +370,9 @@
     if (!buildInfo.androidGradleDaemon) {
       command.add('--no-daemon');
     }
+    if (buildInfo.androidSkipBuildDependencyValidation) {
+      command.add('-PskipDependencyChecks=true');
+    }
     final LocalEngineInfo? localEngineInfo = _artifacts.localEngineInfo;
     if (localEngineInfo != null) {
       final Directory localEngineRepo = _getLocalEngineRepo(
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 59e2459..feca4d7 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -41,6 +41,7 @@
     this.nullSafetyMode = NullSafetyMode.sound,
     this.codeSizeDirectory,
     this.androidGradleDaemon = true,
+    this.androidSkipBuildDependencyValidation = false,
     this.packageConfig = PackageConfig.empty,
     this.initializeFromDill,
     this.assumeInitializeFromDillUpToDate = false,
@@ -153,6 +154,10 @@
   /// The Gradle daemon may also be disabled in the Android application's properties file.
   final bool androidGradleDaemon;
 
+  /// Whether to skip checking of individual versions of our Android build time
+  /// dependencies.
+  final bool androidSkipBuildDependencyValidation;
+
   /// Additional key value pairs that are passed directly to the gradle project via the `-P`
   /// flag.
   final List<String> androidProjectArgs;
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 7067376..87f2f0e 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -147,6 +147,7 @@
   static const String kAndroidGradleDaemon = 'android-gradle-daemon';
   static const String kDeferredComponents = 'deferred-components';
   static const String kAndroidProjectArgs = 'android-project-arg';
+  static const String kAndroidSkipBuildDependencyValidation = 'android-skip-build-dependency-validation';
   static const String kInitializeFromDill = 'initialize-from-dill';
   static const String kAssumeInitializeFromDillUpToDate = 'assume-initialize-from-dill-up-to-date';
   static const String kNativeAssetsYamlFile = 'native-assets-yaml-file';
@@ -974,6 +975,12 @@
       defaultsTo: true,
       hide: hide,
     );
+    argParser.addFlag(
+      FlutterOptions.kAndroidSkipBuildDependencyValidation,
+      help: 'Whether to skip version checking for Java, Gradle, '
+          'the Android Gradle Plugin (AGP), and the Kotlin Gradle Plugin (KGP)'
+          ' during Android builds.',
+    );
     argParser.addMultiOption(
       FlutterOptions.kAndroidProjectArgs,
       help: 'Additional arguments specified as key=value that are passed directly to the gradle '
@@ -1228,6 +1235,9 @@
     final bool androidGradleDaemon = !argParser.options.containsKey(FlutterOptions.kAndroidGradleDaemon)
       || boolArg(FlutterOptions.kAndroidGradleDaemon);
 
+    final bool androidSkipBuildDependencyValidation = !argParser.options.containsKey(FlutterOptions.kAndroidSkipBuildDependencyValidation)
+        || boolArg(FlutterOptions.kAndroidSkipBuildDependencyValidation);
+
     final List<String> androidProjectArgs = argParser.options.containsKey(FlutterOptions.kAndroidProjectArgs)
       ? stringsArg(FlutterOptions.kAndroidProjectArgs)
       : <String>[];
@@ -1316,6 +1326,7 @@
       nullSafetyMode: nullSafetyMode,
       codeSizeDirectory: codeSizeDirectory,
       androidGradleDaemon: androidGradleDaemon,
+      androidSkipBuildDependencyValidation: androidSkipBuildDependencyValidation,
       packageConfig: packageConfig,
       androidProjectArgs: androidProjectArgs,
       initializeFromDill: argParser.options.containsKey(FlutterOptions.kInitializeFromDill)
diff --git a/packages/flutter_tools/test/android_preview_integration.shard/android_dependency_version_checking_test.dart b/packages/flutter_tools/test/android_preview_integration.shard/android_dependency_version_checking_test.dart
new file mode 100644
index 0000000..1906753
--- /dev/null
+++ b/packages/flutter_tools/test/android_preview_integration.shard/android_dependency_version_checking_test.dart
@@ -0,0 +1,208 @@
+// 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 'dart:io';
+
+import 'package:file/src/interface/file_system_entity.dart';
+
+import '../integration.shard/test_utils.dart';
+import '../src/common.dart';
+import '../src/context.dart';
+
+const String gradleSettingsFileContent = r'''
+pluginManagement {
+    def flutterSdkPath = {
+        def properties = new Properties()
+        file("local.properties").withInputStream { properties.load(it) }
+        def flutterSdkPath = properties.getProperty("flutter.sdk")
+        assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
+        return flutterSdkPath
+    }()
+
+    includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
+
+    repositories {
+        google()
+        mavenCentral()
+        gradlePluginPortal()
+    }
+}
+
+plugins {
+    id "dev.flutter.flutter-plugin-loader" version "1.0.0"
+    id "com.android.application" version "AGP_REPLACE_ME" apply false
+    id "org.jetbrains.kotlin.android" version "KGP_REPLACE_ME" apply false
+}
+
+include ":app"
+
+''';
+
+const String agpReplacementString = 'AGP_REPLACE_ME';
+const String kgpReplacementString = 'KGP_REPLACE_ME';
+
+const String gradleWrapperPropertiesFileContent = r'''
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-GRADLE_REPLACE_ME-all.zip
+
+''';
+
+const String gradleReplacementString = 'GRADLE_REPLACE_ME';
+
+// This test is currently on the preview shard (but not using the preview
+// version of Android) because it is the only one using Java 11. This test
+// requires Java 11 due to the intentionally low version of Gradle.
+void main() {
+  late Directory tempDir;
+
+  setUpAll(() async {
+    tempDir = createResolvedTempDirectorySync('run_test.');
+  });
+
+  tearDownAll(() async {
+    tryToDelete(tempDir as FileSystemEntity);
+  });
+
+  testUsingContext(
+      'AGP version out of "warn" support band prints warning but still builds', () async {
+    // Create a new flutter project.
+    final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
+    ProcessResult result = await processManager.run(<String>[
+      flutterBin,
+      'create',
+      'dependency_checker_app',
+      '--platforms=android',
+    ], workingDirectory: tempDir.path);
+    expect(result, const ProcessResultMatcher());
+    const String gradleVersion = '7.5';
+    const String agpVersion = '4.2.0';
+    const String kgpVersion = '1.7.10';
+
+    final Directory app = Directory(fileSystem.path.join(tempDir.path, 'dependency_checker_app'));
+
+    // Modify gradle version to passed in version.
+    final File gradleWrapperProperties = File(fileSystem.path.join(
+        app.path, 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties'));
+    final String propertyContent = gradleWrapperPropertiesFileContent.replaceFirst(
+      gradleReplacementString,
+      gradleVersion,
+    );
+    await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
+
+    final File gradleSettings = File(fileSystem.path.join(
+        app.path, 'android', 'settings.gradle'));
+    final String settingsContent = gradleSettingsFileContent
+        .replaceFirst(agpReplacementString, agpVersion)
+        .replaceFirst(kgpReplacementString, kgpVersion);
+    await gradleSettings.writeAsString(settingsContent, flush: true);
+
+
+    // Ensure that gradle files exists from templates.
+    result = await processManager.run(<String>[
+      flutterBin,
+      'build',
+      'apk',
+      '--debug',
+    ], workingDirectory: app.path);
+    expect(result, const ProcessResultMatcher());
+    expect(result.stderr, contains('Please upgrade your Android Gradle '
+        'Plugin version'));
+  });
+
+  testUsingContext(
+      'Gradle version out of "warn" support band prints warning but still builds', () async {
+    // Create a new flutter project.
+    final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
+    ProcessResult result = await processManager.run(<String>[
+      flutterBin,
+      'create',
+      'dependency_checker_app',
+      '--platforms=android',
+    ], workingDirectory: tempDir.path);
+    expect(result, const ProcessResultMatcher());
+    const String gradleVersion = '7.0';
+    const String agpVersion = '4.2.0';
+    const String kgpVersion = '1.7.10';
+
+    final Directory app = Directory(fileSystem.path.join(tempDir.path, 'dependency_checker_app'));
+
+    // Modify gradle version to passed in version.
+    final File gradleWrapperProperties = File(fileSystem.path.join(
+        app.path, 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties'));
+    final String propertyContent = gradleWrapperPropertiesFileContent.replaceFirst(
+      gradleReplacementString,
+      gradleVersion,
+    );
+    await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
+
+    final File gradleSettings = File(fileSystem.path.join(
+        app.path, 'android', 'settings.gradle'));
+    final String settingsContent = gradleSettingsFileContent
+        .replaceFirst(agpReplacementString, agpVersion)
+        .replaceFirst(kgpReplacementString, kgpVersion);
+    await gradleSettings.writeAsString(settingsContent, flush: true);
+
+
+    // Ensure that gradle files exists from templates.
+    result = await processManager.run(<String>[
+      flutterBin,
+      'build',
+      'apk',
+      '--debug',
+    ], workingDirectory: app.path);
+    expect(result, const ProcessResultMatcher());
+    expect(result.stderr, contains('Please upgrade your Gradle version'));
+  });
+
+  testUsingContext(
+      'Kotlin version out of "warn" support band prints warning but still builds', () async {
+    // Create a new flutter project.
+    final String flutterBin = fileSystem.path.join(getFlutterRoot(), 'bin', 'flutter');
+    ProcessResult result = await processManager.run(<String>[
+      flutterBin,
+      'create',
+      'dependency_checker_app',
+      '--platforms=android',
+    ], workingDirectory: tempDir.path);
+    expect(result, const ProcessResultMatcher());
+    const String gradleVersion = '7.5';
+    const String agpVersion = '7.4.0';
+    const String kgpVersion = '1.4.10';
+
+    final Directory app = Directory(fileSystem.path.join(tempDir.path, 'dependency_checker_app'));
+
+    // Modify gradle version to passed in version.
+    final File gradleWrapperProperties = File(fileSystem.path.join(
+        app.path, 'android', 'gradle', 'wrapper', 'gradle-wrapper.properties'));
+    final String propertyContent = gradleWrapperPropertiesFileContent.replaceFirst(
+      gradleReplacementString,
+      gradleVersion,
+    );
+    await gradleWrapperProperties.writeAsString(propertyContent, flush: true);
+
+    final File gradleSettings = File(fileSystem.path.join(
+        app.path, 'android', 'settings.gradle'));
+    final String settingsContent = gradleSettingsFileContent
+        .replaceFirst(agpReplacementString, agpVersion)
+        .replaceFirst(kgpReplacementString, kgpVersion);
+    await gradleSettings.writeAsString(settingsContent, flush: true);
+
+
+    // Ensure that gradle files exists from templates.
+    result = await processManager.run(<String>[
+      flutterBin,
+      'build',
+      'apk',
+      '--debug',
+    ], workingDirectory: app.path);
+    expect(result, const ProcessResultMatcher());
+    expect(result.stderr, contains('Please upgrade your Kotlin version'));
+  });
+
+  // TODO(gmackall): Add tests for build blocking when the
+  // corresponding error versions are enabled.
+}