Reland #39157 (#39798)

diff --git a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
index 3f3b5c1..d50bab8 100644
--- a/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
+++ b/dev/devicelab/bin/tasks/gradle_plugin_light_apk_test.dart
@@ -52,7 +52,6 @@
           'assets/flutter_assets/isolate_snapshot_data',
           'assets/flutter_assets/kernel_blob.bin',
           'assets/flutter_assets/vm_snapshot_data',
-          'lib/armeabi-v7a/libflutter.so',
           // Debug mode intentionally includes `x86` and `x86_64`.
           'lib/x86/libflutter.so',
           'lib/x86_64/libflutter.so',
@@ -79,7 +78,6 @@
           'assets/flutter_assets/isolate_snapshot_data',
           'assets/flutter_assets/kernel_blob.bin',
           'assets/flutter_assets/vm_snapshot_data',
-          'lib/armeabi-v7a/libflutter.so',
           // Debug mode intentionally includes `x86` and `x86_64`.
           'lib/x86/libflutter.so',
           'lib/x86_64/libflutter.so',
diff --git a/examples/platform_view/android/app/build.gradle b/examples/platform_view/android/app/build.gradle
index b80bbcd..14ce0b1 100644
--- a/examples/platform_view/android/app/build.gradle
+++ b/examples/platform_view/android/app/build.gradle
@@ -57,6 +57,6 @@
     testImplementation 'junit:junit:4.12'
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
-    implementation 'com.android.support:appcompat-v7:26.1.0'
-    implementation 'com.android.support:design:26.1.0'
+    implementation 'com.android.support:appcompat-v7:28.0.0'
+    implementation 'com.android.support:design:28.0.0'
 }
diff --git a/packages/flutter_tools/gradle/flutter.gradle b/packages/flutter_tools/gradle/flutter.gradle
index c3328d7..3e76802 100644
--- a/packages/flutter_tools/gradle/flutter.gradle
+++ b/packages/flutter_tools/gradle/flutter.gradle
@@ -42,6 +42,8 @@
 apply plugin: FlutterPlugin
 
 class FlutterPlugin implements Plugin<Project> {
+    private static final String MAVEN_REPO      = "http://download.flutter.io";
+
     // The platforms that can be passed to the `--Ptarget-platform` flag.
     private static final String PLATFORM_ARM32  = "android-arm";
     private static final String PLATFORM_ARM64  = "android-arm64";
@@ -85,6 +87,7 @@
     // to match.
     static final String FLUTTER_BUILD_PREFIX = "flutterBuild"
 
+    private Project project
     private Map baseJar = [:]
     private File flutterRoot
     private File flutterExecutable
@@ -92,16 +95,18 @@
     private String localEngineSrcPath
     private Properties localProperties
     private File flutterJar
+    private String engineVersion
 
     @Override
     void apply(Project project) {
+        this.project = project
+
         project.extensions.create("flutter", FlutterExtension)
         project.afterEvaluate this.&addFlutterTasks
-
         // By default, assembling APKs generates fat APKs if multiple platforms are passed.
         // Configuring split per ABI allows to generate separate APKs for each abi.
         // This is a noop when building a bundle.
-        if (splitPerAbi(project)) {
+        if (shouldSplitPerAbi()) {
             project.android {
                 splits {
                     abi {
@@ -115,14 +120,10 @@
                 }
             }
         }
-        getTargetPlatforms(project).each { targetArch ->
+        getTargetPlatforms().each { targetArch ->
             String abiValue = PLATFORM_ARCH_MAP[targetArch]
             project.android {
-                packagingOptions {
-                    // Prevent the ELF library from getting corrupted.
-                    doNotStrip "*/${abiValue}/libapp.so"
-                }
-                if (splitPerAbi(project)) {
+                if (shouldSplitPerAbi()) {
                     splits {
                         abi {
                             include abiValue
@@ -132,7 +133,7 @@
             }
         }
 
-        String flutterRootPath = resolveProperty(project, "flutter.sdk", System.env.FLUTTER_ROOT)
+        String flutterRootPath = resolveProperty("flutter.sdk", System.env.FLUTTER_ROOT)
         if (flutterRootPath == null) {
             throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file or with a FLUTTER_ROOT environment variable.")
         }
@@ -141,6 +142,9 @@
             throw new GradleException("flutter.sdk must point to the Flutter SDK directory")
         }
 
+        engineVersion = Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version")
+                .toFile().text.trim()
+
         String flutterExecutableName = Os.isFamily(Os.FAMILY_WINDOWS) ? "flutter.bat" : "flutter"
         flutterExecutable = Paths.get(flutterRoot.absolutePath, "bin", flutterExecutableName).toFile();
 
@@ -173,7 +177,7 @@
             }
         }
 
-        if (useLocalEngine(project)) {
+        if (useLocalEngine()) {
             String engineOutPath = project.property('localEngineOut')
             File engineOut = project.file(engineOutPath)
             if (!engineOut.isDirectory()) {
@@ -194,45 +198,48 @@
                 })
             }
         } else {
-            String basePlatformArch = getBasePlatform(project)
-            // This is meant to include the compiled classes only, however it will include `libflutter.so` as well.
-            Path baseEnginePath = Paths.get(flutterRoot.absolutePath, "bin", "cache", "artifacts", "engine")
-            File debugJar = baseEnginePath.resolve("${basePlatformArch}").resolve("flutter.jar").toFile()
-            baseJar["debug"] = debugJar
-            if (!debugJar.isFile()) {
-                project.exec {
-                    executable flutterExecutable.absolutePath
-                    args "--suppress-analytics"
-                    args "precache"
-                }
-                if (!debugJar.isFile()) {
-                    throw new GradleException("Unable to find flutter.jar in SDK: ${debugJar}")
-                }
-            }
-            baseJar["profile"] = baseEnginePath.resolve("${basePlatformArch}-profile").resolve("flutter.jar").toFile()
-            baseJar["release"] = baseEnginePath.resolve("${basePlatformArch}-release").resolve("flutter.jar").toFile()
+            project.android.buildTypes.each this.&addFlutterDependencies
+            project.android.buildTypes.whenObjectAdded this.&addFlutterDependencies
+        }
+    }
 
-            // Add flutter.jar dependencies to all <buildType>Api configurations, including custom ones
-            // added after applying the Flutter plugin.
-            project.android.buildTypes.each {
-                def buildMode = buildModeFor(it)
-                addApiDependencies(project, it.name, project.files {
-                    baseJar[buildMode]
-                })
+    /**
+     * Adds the dependencies required by the Flutter project.
+     * This includes:
+     *    1. The embedding
+     *    2. libflutter.so
+     */
+    void addFlutterDependencies(buildType) {
+        project.rootProject.allprojects {
+            repositories {
+                maven {
+                    url MAVEN_REPO
+                }
             }
-            project.android.buildTypes.whenObjectAdded {
-                def buildMode = buildModeFor(it)
-                addApiDependencies(project, it.name, project.files {
-                    baseJar[buildMode]
-                })
-            }
+        }
+        String flutterBuildMode = buildModeFor(buildType)
+        // Add the embedding dependency.
+        addApiDependencies(project, buildType.name,
+                "io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion")
+
+        List<String> platforms = getTargetPlatforms().collect()
+        // Debug mode includes x86 and x64, which are commonly used in emulators.
+        if (flutterBuildMode == "debug") {
+            platforms.add("android-x86")
+            platforms.add("android-x64")
+        }
+        platforms.each { platform ->
+            String arch = PLATFORM_ARCH_MAP[platform].replace("-", "_")
+            // Add the `libflutter.so` dependency.
+            addApiDependencies(project, buildType.name,
+                    "io.flutter:${arch}_$flutterBuildMode:1.0.0-$engineVersion")
         }
     }
 
     /**
      * Returns the directory where the plugins are built.
      */
-    private File getPluginBuildDir(Project project) {
+    private File getPluginBuildDir() {
         // Module projects specify this flag to include plugins in the same repo as the module project.
         if (project.ext.has("pluginBuildDir")) {
             return project.ext.get("pluginBuildDir")
@@ -240,13 +247,93 @@
         return project.buildDir
     }
 
-    private Properties getPluginList(Project project) {
+    /**
+     * Configures the Flutter plugin dependencies.
+     *
+     * The plugins are added to pubspec.yaml. Then, upon running `flutter pub get`,
+     * the tool generates a `.flutter-plugins` file, which contains a 1:1 map to each plugin location.
+     * Finally, the project's `settings.gradle` loads each plugin's android directory as a subproject.
+     */
+    private void configurePlugins() {
+        if (!buildPluginAsAar()) {
+            getPluginList().each this.&configurePlugin
+            return
+        }
+        addPluginTasks()
+        List<String> tasksToExecute = project.gradle.startParameter.taskNames
+        Set buildTypes = getBuildTypesForTasks(tasksToExecute)
+        if (tasksToExecute.contains("clean")) {
+            // Because the plugins are built during configuration, the task "clean"
+            // cannot run in conjunction with an assembly task.
+            if (!buildTypes.empty) {
+                throw new GradleException("Can't run the clean task along with other assemble tasks")
+            }
+        }
+        // Build plugins when a task "assembly*" will be called later.
+        if (!buildTypes.empty) {
+            // Build the plugin during configuration.
+            // This is required when Jetifier is enabled, otherwise the implementation dependency
+            // cannot be added.
+            buildAarPlugins(buildTypes)
+        }
+    }
+
+    private void configurePlugin(String name, String _) {
+        Project pluginProject = project.rootProject.findProject(":$name")
+        if (pluginProject == null) {
+            project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
+            return
+        }
+        // Add plugin dependency to the app project.
+        project.dependencies {
+            if (project.getConfigurations().findByName("implementation")) {
+                implementation pluginProject
+            } else {
+                compile pluginProject
+            }
+        }
+        Closure addEmbeddingCompileOnlyDependency = { buildType ->
+            String flutterBuildMode = buildModeFor(buildType)
+            // Add the embedding as a compile only dependency to the plugin.
+            if (flutterJar) {
+                addCompileOnlyDependency(
+                    pluginProject,
+                    buildType.name,
+                    project.files {
+                        flutterJar
+                    }
+                )
+                return
+            }
+            addCompileOnlyDependency(
+                pluginProject,
+                buildType.name,
+                "io.flutter:flutter_embedding_$flutterBuildMode:1.0.0-$engineVersion",
+                {
+                    // We only need to expose io.flutter.plugin.*
+                    // No need for the embedding transitive dependencies.
+                    transitive = false
+                }
+            )
+        }
+        pluginProject.afterEvaluate {
+            pluginProject.android.buildTypes {
+                profile {
+                    initWith debug
+                }
+            }
+            pluginProject.android.buildTypes.each addEmbeddingCompileOnlyDependency
+            pluginProject.android.buildTypes.whenObjectAdded addEmbeddingCompileOnlyDependency
+        }
+    }
+
+    private Properties getPluginList() {
         File pluginsFile = new File(project.projectDir.parentFile.parentFile, '.flutter-plugins')
         return readPropertiesIfExist(pluginsFile)
     }
 
-    private void addPluginTasks(Project project) {
-        Properties plugins = getPluginList(project)
+    private void addPluginTasks() {
+        Properties plugins = getPluginList()
         project.android.buildTypes.each { buildType ->
             plugins.each { name, path ->
                 String buildModeValue = buildType.debuggable ? "debug" : "release"
@@ -259,17 +346,17 @@
                     project.tasks.create(name: taskName, type: FlutterPluginTask) {
                         flutterExecutable this.flutterExecutable
                         buildMode buildModeValue
-                        verbose isVerbose(project)
+                        verbose isVerbose()
                         pluginDir project.file(path)
                         sourceDir project.file(project.flutter.source)
-                        intermediateDir getPluginBuildDir(project)
+                        intermediateDir getPluginBuildDir()
                     }
                 }
             }
         }
     }
 
-    private void buildPlugins(Project project, Set buildTypes) {
+    private void buildAarPlugins(Set buildTypes) {
         List<Project> projects = [project]
         // Module projects set the `hostProjects` extra property in `include_flutter.groovy`.
         // This is required to set the local repository in each host app project.
@@ -279,7 +366,7 @@
         projects.each { hostProject ->
             hostProject.repositories {
                 maven {
-                    url "${getPluginBuildDir(project)}/outputs/repo"
+                    url "${getPluginBuildDir()}/outputs/repo"
                 }
             }
         }
@@ -314,7 +401,7 @@
      * Returns a set with the build type names that apply to the given list of tasks
      * required to configure the plugin dependencies.
      */
-    private Set getBuildTypesForTasks(Project project, List<String> tasksToExecute) {
+    private Set getBuildTypesForTasks(List<String> tasksToExecute) {
         Set buildTypes = []
         tasksToExecute.each { task ->
             project.android.buildTypes.each { buildType ->
@@ -339,7 +426,7 @@
         return "${parts[0]}${parts[1..-1].collect { it.capitalize() }.join('')}"
     }
 
-    private String resolveProperty(Project project, String name, String defaultValue) {
+    private String resolveProperty(String name, String defaultValue) {
         if (localProperties == null) {
             localProperties = readPropertiesIfExist(new File(project.projectDir.parentFile, "local.properties"))
         }
@@ -364,7 +451,7 @@
         return result
     }
 
-    private static List<String> getTargetPlatforms(Project project) {
+    private List<String> getTargetPlatforms() {
         if (!project.hasProperty('target-platform')) {
             return DEFAULT_PLATFORMS
         }
@@ -376,18 +463,18 @@
         }
     }
 
-    private static Boolean splitPerAbi(Project project) {
+    private Boolean shouldSplitPerAbi() {
         if (project.hasProperty('split-per-abi')) {
             return project.property('split-per-abi').toBoolean()
         }
         return false;
     }
 
-    private static Boolean useLocalEngine(Project project) {
+    private Boolean useLocalEngine() {
         return project.hasProperty('localEngineOut')
     }
 
-    private static Boolean isVerbose(Project project) {
+    private Boolean isVerbose() {
         if (project.hasProperty('verbose')) {
             return project.property('verbose').toBoolean()
         }
@@ -411,20 +498,7 @@
         return System.getProperty('build-plugins-as-aars') == 'true'
     }
 
-    /**
-     * Returns the platform that is used to extract the `libflutter.so` and the .class files.
-     *
-     * Note: This is only needed to add the .class files.
-     * Unfortunately, the engine artifacts include the .class and libflutter.so files.
-     */
-    private static String getBasePlatform(Project project) {
-        if (PLATFORM_ARM64 in getTargetPlatforms(project)) {
-            return PLATFORM_ARM64;
-        }
-        return PLATFORM_ARM32;
-    }
-
-    private void addFlutterJarCompileOnlyDependency(Project project, String variantName, FileCollection files) {
+    private void addCompileOnlyDependency(Project project, String variantName, Object dependency, Closure config = null) {
         if (project.state.failure) {
             return
         }
@@ -434,7 +508,7 @@
         } else {
             configuration = "${variantName}Provided";
         }
-        project.dependencies.add(configuration, files)
+        project.dependencies.add(configuration, dependency, config)
     }
 
     private static void addApiDependencies(Project project, String variantName, Object dependency, Closure config = null) {
@@ -487,7 +561,6 @@
         if (project.hasProperty('target')) {
             target = project.property('target')
         }
-
         String[] fileSystemRootsValue = null
         if (project.hasProperty('filesystem-roots')) {
             fileSystemRootsValue = project.property('filesystem-roots').split('\\|')
@@ -524,10 +597,9 @@
         if (project.hasProperty('extra-gen-snapshot-options')) {
             extraGenSnapshotOptionsValue = project.property('extra-gen-snapshot-options')
         }
-
-        def targetPlatforms = getTargetPlatforms(project)
+        def targetPlatforms = getTargetPlatforms()
         def addFlutterDeps = { variant ->
-            if (splitPerAbi(project)) {
+            if (shouldSplitPerAbi()) {
                 variant.outputs.each { output ->
                     // Assigns the new version code to versionCodeOverride, which changes the version code
                     // for only the output APK, not for the variant itself. Skipping this step simply
@@ -540,31 +612,17 @@
                     }
                 }
             }
-
-            String flutterBuildMode = buildModeFor(variant.buildType)
-            if (flutterBuildMode == 'debug' && project.tasks.findByName("${FLUTTER_BUILD_PREFIX}X86Jar")) {
-                Task task = project.tasks.findByName("compile${variant.name.capitalize()}JavaWithJavac")
-                if (task) {
-                    task.dependsOn project.flutterBuildX86Jar
-                }
-                task = project.tasks.findByName("compile${variant.name.capitalize()}Kotlin")
-                if (task) {
-                    task.dependsOn project.flutterBuildX86Jar
-                }
-            }
-
             def compileTasks = targetPlatforms.collect { targetArch ->
-                String abiValue = PLATFORM_ARCH_MAP[targetArch]
                 String taskName = toCammelCase(["compile", FLUTTER_BUILD_PREFIX, variant.name, targetArch.replace('android-', '')])
-                FlutterTask compileTask = project.tasks.create(name: taskName, type: FlutterTask) {
+                project.tasks.create(name: taskName, type: FlutterTask) {
                     flutterRoot this.flutterRoot
                     flutterExecutable this.flutterExecutable
-                    buildMode flutterBuildMode
+                    buildMode buildModeFor(variant.buildType)
                     localEngine this.localEngine
                     localEngineSrcPath this.localEngineSrcPath
-                    abi abiValue
+                    abi PLATFORM_ARCH_MAP[targetArch]
                     targetPath target
-                    verbose isVerbose(project)
+                    verbose isVerbose()
                     fileSystemRoots fileSystemRootsValue
                     fileSystemScheme fileSystemSchemeValue
                     trackWidgetCreation trackWidgetCreationValue
@@ -579,51 +637,24 @@
                     extraGenSnapshotOptions extraGenSnapshotOptionsValue
                 }
             }
-
             def libJar = project.file("${project.buildDir}/${AndroidProject.FD_INTERMEDIATES}/flutter/${variant.name}/libs.jar")
-            def libFlutterPlatforms = targetPlatforms.collect()
-            // x86/x86_64 native library used for debugging only, for now.
-            if (flutterBuildMode == 'debug') {
-                libFlutterPlatforms.add('android-x86')
-                libFlutterPlatforms.add('android-x64')
-            }
-            Task packFlutterSnapshotsAndLibsTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
+            Task packFlutterAppAotTask = project.tasks.create(name: "packLibs${FLUTTER_BUILD_PREFIX}${variant.name.capitalize()}", type: Jar) {
                 destinationDir libJar.parentFile
                 archiveName libJar.name
-                libFlutterPlatforms.each { targetArch ->
-                    // This check prevents including `libflutter.so` twice, since it's included in the base platform jar.
-                    // Unfortunately, the `pickFirst` setting in `packagingOptions` does not work when the project `:flutter`
-                    // is included as an implementation dependency, which causes duplicated `libflutter.so`.
-                    if (getBasePlatform(project) == targetArch) {
-                        return
-                    }
-                    // Don't include `libflutter.so` for other architectures when a local engine is specified.
-                    if (useLocalEngine(project)) {
-                        return
-                    }
-                    def engineArtifactSubdir = getEngineArtifactDirName(variant.buildType, targetArch);
-                    // Include `libflutter.so`.
-                    // TODO(blasten): The libs should be outside `flutter.jar` when the artifacts are downloaded.
-                    from(project.zipTree("${flutterRoot}/bin/cache/artifacts/engine/${engineArtifactSubdir}/flutter.jar")) {
-                        include 'lib/**'
-                    }
-                }
                 dependsOn compileTasks
-                // Add the ELF library.
                 compileTasks.each { compileTask ->
                     from(compileTask.intermediateDir) {
                         include '*.so'
+                        // Move `app.so` to `lib/<abi>/libapp.so`
                         rename { String filename ->
                             return "lib/${compileTask.abi}/lib${filename}"
                         }
                     }
                 }
             }
-            // Include the snapshots and libflutter.so in `lib/`.
             addApiDependencies(project, variant.name, project.files {
-                packFlutterSnapshotsAndLibsTask
+                packFlutterAppAotTask
             })
-
             // We know that the flutter app is a subproject in another Android app when these tasks exist.
             Task packageAssets = project.tasks.findByPath(":flutter:package${variant.name.capitalize()}Assets")
             Task cleanPackageAssets = project.tasks.findByPath(":flutter:cleanPackage${variant.name.capitalize()}Assets")
@@ -650,57 +681,7 @@
         } else {
             project.android.libraryVariants.all addFlutterDeps
         }
-
-        if (buildPluginAsAar()) {
-            addPluginTasks(project)
-
-            List<String> tasksToExecute = project.gradle.startParameter.taskNames
-            Set buildTypes = getBuildTypesForTasks(project, tasksToExecute)
-            if (tasksToExecute.contains("clean")) {
-                // Because the plugins are built during configuration, the task "clean"
-                // cannot run in conjunction with an assembly task.
-                if (!buildTypes.empty) {
-                    throw new GradleException("Can't run the clean task along with other assemble tasks")
-                }
-            }
-            // Build plugins when a task "assembly*" will be called later.
-            if (!buildTypes.empty) {
-                // Build the plugin during configuration.
-                // This is required when Jetifier is enabled, otherwise the implementation dependency
-                // cannot be added.
-                buildPlugins(project, buildTypes)
-            }
-        } else {
-            getPluginList(project).each { name, _ ->
-                def pluginProject = project.rootProject.findProject(":$name")
-                if (pluginProject != null) {
-                    project.dependencies {
-                        if (project.getConfigurations().findByName("implementation")) {
-                            implementation pluginProject
-                        } else {
-                            compile pluginProject
-                        }
-                    }
-                    pluginProject.afterEvaluate {
-                        pluginProject.android.buildTypes {
-                            profile {
-                                initWith debug
-                            }
-                        }
-                        pluginProject.android.buildTypes.each {
-                            def buildMode = buildModeFor(it)
-                            addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
-                        }
-                        pluginProject.android.buildTypes.whenObjectAdded {
-                            def buildMode = buildModeFor(it)
-                            addFlutterJarCompileOnlyDependency(pluginProject, it.name, project.files( flutterJar ?: baseJar[buildMode] ))
-                        }
-                    }
-                } else {
-                    project.logger.error("Plugin project :$name not found. Please update settings.gradle.")
-                }
-            }
-        }
+        configurePlugins()
     }
 }
 
diff --git a/packages/flutter_tools/gradle/flutter_proguard_rules.pro b/packages/flutter_tools/gradle/flutter_proguard_rules.pro
index 8fc13e7..10390ef 100644
--- a/packages/flutter_tools/gradle/flutter_proguard_rules.pro
+++ b/packages/flutter_tools/gradle/flutter_proguard_rules.pro
@@ -1,7 +1,3 @@
-# Prevents `Fragment and FragmentActivity not found`.
-# TODO(blasten): Remove once we bring the Maven dependencies.
--dontwarn io.flutter.embedding.**
-
 # Build the ephemeral app in a module project.
 # Prevents: Warning: library class <plugin-package> depends on program class io.flutter.plugin.**
 # This is due to plugins (libraries) depending on the embedding (the program jar)
diff --git a/packages/flutter_tools/gradle/resolve_dependencies.gradle b/packages/flutter_tools/gradle/resolve_dependencies.gradle
new file mode 100644
index 0000000..2061bc8
--- /dev/null
+++ b/packages/flutter_tools/gradle/resolve_dependencies.gradle
@@ -0,0 +1,64 @@
+// Copyright 2019 The Chromium Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style license that can be
+// found in the LICENSE file.
+//
+//
+// This script is used to warm the Gradle cache by downloading the Flutter dependencies
+// used during the build. This script is invoked when `flutter precache` is run.
+//
+// Command:
+//  gradle -b <flutter-sdk>packages/flutter_tools/gradle/resolve_dependencies.gradle
+//      resolveDependencies
+//
+// This way, Gradle can run with the `--offline` flag later on to eliminate any
+// network request during the build process.
+//
+// This includes:
+//   1. The embedding
+//   2. libflutter.so
+
+import java.nio.file.Paths
+
+repositories {
+    google()
+    jcenter()
+    maven {
+        url "http://download.flutter.io"
+    }
+}
+
+File flutterRoot = projectDir.parentFile.parentFile.parentFile
+
+assert flutterRoot.isDirectory()
+String engineVersion = Paths.get(flutterRoot.absolutePath, "bin", "internal", "engine.version")
+        .toFile().text.trim()
+
+configurations {
+    flutterRelease.extendsFrom releaseImplementation
+    flutterDebug.extendsFrom debugImplementation
+    flutterProfile.extendsFrom debugImplementation
+}
+
+dependencies {
+    flutterRelease "io.flutter:flutter_embedding_release:1.0.0-$engineVersion"
+    flutterRelease "io.flutter:armeabi_v7a_release:1.0.0-$engineVersion"
+    flutterRelease "io.flutter:arm64_v8a_release:1.0.0-$engineVersion"
+
+    flutterProfile "io.flutter:flutter_embedding_profile:1.0.0-$engineVersion"
+    flutterProfile "io.flutter:armeabi_v7a_profile:1.0.0-$engineVersion"
+    flutterProfile "io.flutter:arm64_v8a_profile:1.0.0-$engineVersion"
+
+    flutterDebug "io.flutter:flutter_embedding_debug:1.0.0-$engineVersion"
+    flutterDebug "io.flutter:armeabi_v7a_debug:1.0.0-$engineVersion"
+    flutterDebug "io.flutter:arm64_v8a_debug:1.0.0-$engineVersion"
+    flutterDebug "io.flutter:x86_debug:1.0.0-$engineVersion"
+    flutterDebug "io.flutter:x86_64_debug:1.0.0-$engineVersion"
+}
+
+task resolveDependencies {
+    configurations.each { configuration ->
+        if (configuration.name.startsWith("flutter")) {
+            configuration.resolve()
+        }
+    }
+}
diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart
index 6072917..fec973c 100644
--- a/packages/flutter_tools/lib/executable.dart
+++ b/packages/flutter_tools/lib/executable.dart
@@ -84,7 +84,7 @@
     LogsCommand(),
     MakeHostAppEditableCommand(),
     PackagesCommand(),
-    PrecacheCommand(),
+    PrecacheCommand(verboseHelp: verboseHelp),
     RunCommand(verboseHelp: verboseHelp),
     ScreenshotCommand(),
     ShellCompletionCommand(),
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 84523c3..f383329 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -149,7 +149,7 @@
     <String>[gradlew, 'dependencies'],
     throwOnError: true,
     workingDirectory: flutterProject.android.hostAppGradleRoot.path,
-    environment: _gradleEnv,
+    environment: gradleEnv,
   );
   androidSdk.reinitialize();
   progress.stop();
@@ -239,13 +239,13 @@
       <String>[gradlew, if (isLibrary) 'properties' else 'app:properties'],
       throwOnError: true,
       workingDirectory: hostAppGradleRoot.path,
-      environment: _gradleEnv,
+      environment: gradleEnv,
     );
     final RunResult tasksRunResult = await processUtils.run(
       <String>[gradlew, if (isLibrary) 'tasks' else 'app:tasks', '--all', '--console=auto'],
       throwOnError: true,
       workingDirectory: hostAppGradleRoot.path,
-      environment: _gradleEnv,
+      environment: gradleEnv,
     );
     project = GradleProject.fromAppProperties(propertiesRunResult.stdout, tasksRunResult.stdout);
   } catch (exception) {
@@ -324,7 +324,7 @@
     await processUtils.run(
       <String>[gradle, '-v'],
       throwOnError: true,
-      environment: _gradleEnv,
+      environment: gradleEnv,
     );
   } on ProcessException catch (e) {
     final String error = e.toString();
@@ -640,7 +640,7 @@
       command,
       workingDirectory: project.android.hostAppGradleRoot.path,
       allowReentrantFlutter: true,
-      environment: _gradleEnv,
+      environment: gradleEnv,
       mapFunction: (String line) {
         // Always print the full line in verbose mode.
         if (logger.isVerbose) {
@@ -678,7 +678,7 @@
     <String>[fs.file(gradlew).absolute.path, 'build'],
     workingDirectory: project.android.hostAppGradleRoot.path,
     allowReentrantFlutter: true,
-    environment: _gradleEnv,
+    environment: gradleEnv,
   );
   status.stop();
   flutterUsage.sendTiming('build', 'gradle-v1', Duration(milliseconds: sw.elapsedMilliseconds));
@@ -809,7 +809,7 @@
       command,
       workingDirectory: flutterProject.android.hostAppGradleRoot.path,
       allowReentrantFlutter: true,
-      environment: _gradleEnv,
+      environment: gradleEnv,
       // TODO(mklim): if AndroidX warnings are no longer required, this
       // mapFunction and all its associated variabled can be replaced with just
       // `filter: ndkMessagefilter`.
@@ -961,7 +961,8 @@
   return null;
 }
 
-Map<String, String> get _gradleEnv {
+/// The environment variables needed to run Gradle.
+Map<String, String> get gradleEnv {
   final Map<String, String> env = Map<String, String>.from(platform.environment);
   if (javaPath != null) {
     // Use java bundled with Android Studio.
diff --git a/packages/flutter_tools/lib/src/cache.dart b/packages/flutter_tools/lib/src/cache.dart
index af4d749..fa57b92 100644
--- a/packages/flutter_tools/lib/src/cache.dart
+++ b/packages/flutter_tools/lib/src/cache.dart
@@ -6,6 +6,7 @@
 
 import 'package:meta/meta.dart';
 
+import 'android/gradle.dart';
 import 'base/common.dart';
 import 'base/context.dart';
 import 'base/file_system.dart';
@@ -14,6 +15,7 @@
 import 'base/net.dart';
 import 'base/os.dart';
 import 'base/platform.dart';
+import 'base/process.dart';
 import 'globals.dart';
 
 /// A tag for a set of development artifacts that need to be cached.
@@ -30,7 +32,10 @@
   final bool unstable;
 
   /// Artifacts required for Android development.
-  static const DevelopmentArtifact android = DevelopmentArtifact._('android');
+  static const DevelopmentArtifact androidGenSnapshot = DevelopmentArtifact._('android_gen_snapshot');
+  static const DevelopmentArtifact androidMaven = DevelopmentArtifact._('android_maven');
+  // Artifacts used for internal builds.
+  static const DevelopmentArtifact androidInternalBuild = DevelopmentArtifact._('android_internal_build');
 
   /// Artifacts required for iOS development.
   static const DevelopmentArtifact iOS = DevelopmentArtifact._('ios');
@@ -58,7 +63,9 @@
 
   /// The values of DevelopmentArtifacts.
   static final List<DevelopmentArtifact> values = <DevelopmentArtifact>[
-    android,
+    androidGenSnapshot,
+    androidMaven,
+    androidInternalBuild,
     iOS,
     web,
     macOS,
@@ -74,12 +81,16 @@
 class Cache {
   /// [rootOverride] is configurable for testing.
   /// [artifacts] is configurable for testing.
-  Cache({ Directory rootOverride, List<CachedArtifact> artifacts }) : _rootOverride = rootOverride {
+  Cache({ Directory rootOverride, List<ArtifactSet> artifacts }) : _rootOverride = rootOverride {
     if (artifacts == null) {
       _artifacts.add(MaterialFonts(this));
-      _artifacts.add(AndroidEngineArtifacts(this));
-      _artifacts.add(IOSEngineArtifacts(this));
+
       _artifacts.add(GradleWrapper(this));
+      _artifacts.add(AndroidMavenArtifacts());
+      _artifacts.add(AndroidGenSnapshotArtifacts(this));
+      _artifacts.add(AndroidInternalBuildArtifacts(this));
+
+      _artifacts.add(IOSEngineArtifacts(this));
       _artifacts.add(FlutterWebSdk(this));
       _artifacts.add(FlutterSdk(this));
       _artifacts.add(WindowsEngineArtifacts(this));
@@ -101,7 +112,7 @@
   ];
 
   final Directory _rootOverride;
-  final List<CachedArtifact> _artifacts = <CachedArtifact>[];
+  final List<ArtifactSet> _artifacts = <ArtifactSet>[];
 
   // Initialized by FlutterCommandRunner on startup.
   static String flutterRoot;
@@ -248,11 +259,16 @@
       return _dyLdLibEntry;
     }
     final List<String> paths = <String>[];
-    for (CachedArtifact artifact in _artifacts) {
-      final String currentPath = artifact.dyLdLibPath;
-      if (currentPath.isNotEmpty) {
-        paths.add(currentPath);
+    for (ArtifactSet artifact in _artifacts) {
+      final Map<String, String> env = artifact.environment;
+      if (env == null || !env.containsKey('DYLD_LIBRARY_PATH')) {
+        continue;
       }
+      final String path = env['DYLD_LIBRARY_PATH'];
+      if (path.isEmpty) {
+        continue;
+      }
+      paths.add(path);
     }
     _dyLdLibEntry = MapEntry<String, String>('DYLD_LIBRARY_PATH', paths.join(':'));
     return _dyLdLibEntry;
@@ -292,7 +308,7 @@
     return isOlderThanReference(entity: entity, referenceFile: flutterToolsStamp);
   }
 
-  bool isUpToDate() => _artifacts.every((CachedArtifact artifact) => artifact.isUpToDate());
+  bool isUpToDate() => _artifacts.every((ArtifactSet artifact) => artifact.isUpToDate());
 
   Future<String> getThirdPartyFile(String urlStr, String serviceName) async {
     final Uri url = Uri.parse(urlStr);
@@ -321,22 +337,27 @@
     if (!_lockEnabled) {
       return;
     }
-    try {
-      for (CachedArtifact artifact in _artifacts) {
-        if (!artifact.isUpToDate()) {
-          await artifact.update(requiredArtifacts);
+    for (ArtifactSet artifact in _artifacts) {
+      if (!requiredArtifacts.contains(artifact.developmentArtifact)) {
+        printTrace('Artifact $artifact is not required, skipping update.');
+        continue;
+      }
+      if (artifact.isUpToDate()) {
+        continue;
+      }
+      try {
+        await artifact.update();
+      } on SocketException catch (e) {
+        if (_hostsBlockedInChina.contains(e.address?.host)) {
+          printError(
+            'Failed to retrieve Flutter tool dependencies: ${e.message}.\n'
+            'If you\'re in China, please see this page: '
+            'https://flutter.dev/community/china',
+            emphasis: true,
+          );
         }
+        rethrow;
       }
-    } on SocketException catch (e) {
-      if (_hostsBlockedInChina.contains(e.address?.host)) {
-        printError(
-          'Failed to retrieve Flutter tool dependencies: ${e.message}.\n'
-          'If you\'re in China, please see this page: '
-          'https://flutter.dev/community/china',
-          emphasis: true,
-        );
-      }
-      rethrow;
     }
   }
 
@@ -357,23 +378,42 @@
   }
 }
 
-/// An artifact managed by the cache.
-abstract class CachedArtifact {
-  CachedArtifact(this.name, this.cache, this.developmentArtifacts);
+/// Representation of a set of artifacts used by the tool.
+abstract class ArtifactSet {
+  ArtifactSet(this.developmentArtifact) : assert(developmentArtifact != null);
 
-  final String name;
+  /// The development artifact.
+  final DevelopmentArtifact developmentArtifact;
+
+  /// [true] if the artifact is up to date.
+  bool isUpToDate();
+
+  /// The environment variables (if any) required to consume the artifacts.
+  Map<String, String> get environment {
+    return const <String, String>{};
+  }
+
+  /// Updates the artifact.
+  Future<void> update();
+}
+
+/// An artifact set managed by the cache.
+abstract class CachedArtifact extends ArtifactSet {
+  CachedArtifact(
+    this.name,
+    this.cache,
+    DevelopmentArtifact developmentArtifact,
+  ) : super(developmentArtifact);
+
   final Cache cache;
 
+  /// The canonical name of the artifact.
+  final String name;
+
   // The name of the stamp file. Defaults to the same as the
   // artifact name.
   String get stampName => name;
 
-  /// Returns a string to be set as environment DYLD_LIBARY_PATH variable
-  String get dyLdLibPath => '';
-
-  /// All development artifacts this cache provides.
-  final Set<DevelopmentArtifact> developmentArtifacts;
-
   Directory get location => cache.getArtifactDirectory(name);
   String get version => cache.getVersionFor(name);
 
@@ -383,6 +423,7 @@
   /// starting from scratch.
   final List<File> _downloadedFiles = <File>[];
 
+  @override
   bool isUpToDate() {
     if (!location.existsSync()) {
       return false;
@@ -393,13 +434,8 @@
     return isUpToDateInner();
   }
 
-  Future<void> update(Set<DevelopmentArtifact> requiredArtifacts) async {
-    // If the set of required artifacts does not include any from this cache,
-    // then we can claim we are up to date to skip downloading.
-    if (!requiredArtifacts.any(developmentArtifacts.contains)) {
-      printTrace('Artifact $this is not required, skipping update.');
-      return;
-    }
+  @override
+  Future<void> update() async {
     if (!location.existsSync()) {
       try {
         location.createSync(recursive: true);
@@ -504,7 +540,7 @@
   MaterialFonts(Cache cache) : super(
     'material_fonts',
     cache,
-    const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
+    DevelopmentArtifact.universal,
   );
 
   @override
@@ -522,7 +558,7 @@
   FlutterWebSdk(Cache cache) : super(
     'flutter_web_sdk',
     cache,
-    const <DevelopmentArtifact>{ DevelopmentArtifact.web },
+    DevelopmentArtifact.web,
   );
 
   @override
@@ -563,8 +599,8 @@
   EngineCachedArtifact(
     this.stampName,
     Cache cache,
-    Set<DevelopmentArtifact> requiredArtifacts,
-  ) : super('engine', cache, requiredArtifacts);
+    DevelopmentArtifact developmentArtifact,
+  ) : super('engine', cache, developmentArtifact);
 
   @override
   final String stampName;
@@ -685,7 +721,7 @@
   FlutterSdk(Cache cache) : super(
     'flutter_sdk',
     cache,
-    const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
+    DevelopmentArtifact.universal,
   );
 
   @override
@@ -719,7 +755,7 @@
   MacOSEngineArtifacts(Cache cache) : super(
     'macos-sdk',
     cache,
-    const <DevelopmentArtifact> { DevelopmentArtifact.macOS },
+    DevelopmentArtifact.macOS,
   );
 
   @override
@@ -741,7 +777,7 @@
   WindowsEngineArtifacts(Cache cache) : super(
     'windows-sdk',
     cache,
-    const <DevelopmentArtifact> { DevelopmentArtifact.windows },
+    DevelopmentArtifact.windows,
   );
 
   @override
@@ -763,7 +799,7 @@
   LinuxEngineArtifacts(Cache cache) : super(
     'linux-sdk',
     cache,
-    const <DevelopmentArtifact> { DevelopmentArtifact.linux },
+    DevelopmentArtifact.linux,
   );
 
   @override
@@ -781,11 +817,12 @@
   List<String> getLicenseDirs() => const <String>[];
 }
 
-class AndroidEngineArtifacts extends EngineCachedArtifact {
-  AndroidEngineArtifacts(Cache cache) : super(
+/// The artifact used to generate snapshots for Android builds.
+class AndroidGenSnapshotArtifacts extends EngineCachedArtifact {
+  AndroidGenSnapshotArtifacts(Cache cache) : super(
     'android-sdk',
     cache,
-    const <DevelopmentArtifact>{ DevelopmentArtifact.android },
+    DevelopmentArtifact.androidGenSnapshot,
   );
 
   @override
@@ -799,23 +836,19 @@
           ..._osxBinaryDirs,
           ..._linuxBinaryDirs,
           ..._windowsBinaryDirs,
-          ..._androidBinaryDirs,
           ..._dartSdks,
         ]
       else if (platform.isWindows)
         ...<List<String>>[
           ..._windowsBinaryDirs,
-          ..._androidBinaryDirs,
         ]
       else if (platform.isMacOS)
         ...<List<String>>[
           ..._osxBinaryDirs,
-          ..._androidBinaryDirs,
         ]
       else if (platform.isLinux)
         ...<List<String>>[
           ..._linuxBinaryDirs,
-          ..._androidBinaryDirs,
         ]
     ];
   }
@@ -824,11 +857,79 @@
   List<String> getLicenseDirs() { return <String>[]; }
 }
 
+/// Artifacts used for internal builds. The flutter tool builds Android projects
+/// using the artifacts cached by [AndroidMavenArtifacts].
+class AndroidInternalBuildArtifacts extends EngineCachedArtifact {
+  AndroidInternalBuildArtifacts(Cache cache) : super(
+    'android-internal-build-artifacts',
+    cache,
+    DevelopmentArtifact.androidInternalBuild,
+  );
+
+  @override
+  List<String> getPackageDirs() => const <String>[];
+
+  @override
+  List<List<String>> getBinaryDirs() {
+    return _androidBinaryDirs;
+  }
+
+  @override
+  List<String> getLicenseDirs() { return <String>[]; }
+}
+
+/// A cached artifact containing the Maven dependencies used to build Android projects.
+class AndroidMavenArtifacts extends ArtifactSet {
+  AndroidMavenArtifacts() : super(DevelopmentArtifact.androidMaven);
+
+  @override
+  Future<void> update() async {
+    final Directory tempDir =
+        fs.systemTempDirectory.createTempSync('gradle_wrapper.');
+    injectGradleWrapperIfNeeded(tempDir);
+
+    final Status status = logger.startProgress('Downloading Android Maven dependencies...',
+        timeout: timeoutConfiguration.slowOperation);
+    final File gradle = tempDir.childFile(
+        platform.isWindows ? 'gradlew.bat' : 'gradlew',
+      );
+    assert(gradle.existsSync());
+    os.makeExecutable(gradle);
+
+    try {
+      final String gradleExecutable = gradle.absolute.path;
+      final String flutterSdk = escapePath(Cache.flutterRoot);
+      final RunResult processResult = await processUtils.run(
+        <String>[
+          gradleExecutable,
+          '-b', fs.path.join(flutterSdk, 'packages', 'flutter_tools', 'gradle', 'resolve_dependencies.gradle'),
+          '--project-cache-dir', tempDir.path,
+          'resolveDependencies',
+        ],
+        environment: gradleEnv);
+      if (processResult.exitCode != 0) {
+        printError('Failed to download the Android dependencies');
+      }
+    } finally {
+      status.stop();
+      tempDir.deleteSync(recursive: true);
+    }
+  }
+
+  @override
+  bool isUpToDate() {
+    // The dependencies are downloaded and cached by Gradle.
+    // The tool doesn't know if the dependencies are already cached at this point.
+    // Therefore, call Gradle to figure this out.
+    return false;
+  }
+}
+
 class IOSEngineArtifacts extends EngineCachedArtifact {
   IOSEngineArtifacts(Cache cache) : super(
     'ios-sdk',
     cache,
-    <DevelopmentArtifact>{ DevelopmentArtifact.iOS },
+    DevelopmentArtifact.iOS,
   );
 
   @override
@@ -861,7 +962,7 @@
   GradleWrapper(Cache cache) : super(
     'gradle_wrapper',
     cache,
-    const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
+    DevelopmentArtifact.universal,
   );
 
   List<String> get _gradleScripts => <String>['gradlew', 'gradlew.bat'];
@@ -904,11 +1005,13 @@
 
 /// Common functionality for pulling Fuchsia SDKs.
 abstract class _FuchsiaSDKArtifacts extends CachedArtifact {
-  _FuchsiaSDKArtifacts(Cache cache, String platform)
-      :_path = 'fuchsia/sdk/core/$platform-amd64',
-       super('fuchsia-$platform', cache, const <DevelopmentArtifact> {
-    DevelopmentArtifact.fuchsia,
-  });
+  _FuchsiaSDKArtifacts(Cache cache, String platform) :
+    _path = 'fuchsia/sdk/core/$platform-amd64',
+    super(
+      'fuchsia-$platform',
+      cache,
+      DevelopmentArtifact.fuchsia,
+    );
 
   final String _path;
 
@@ -924,10 +1027,11 @@
 
 /// The pre-built flutter runner for Fuchsia development.
 class FlutterRunnerSDKArtifacts extends CachedArtifact {
-  FlutterRunnerSDKArtifacts(Cache cache)
-      : super('flutter_runner', cache, const <DevelopmentArtifact>{
+  FlutterRunnerSDKArtifacts(Cache cache) : super(
+    'flutter_runner',
+    cache,
     DevelopmentArtifact.flutterRunner,
-  });
+  );
 
   @override
   Directory get location => cache.getArtifactDirectory('flutter_runner');
@@ -978,7 +1082,7 @@
     name,
     cache,
     // This is universal to ensure every command checks for them first
-    const <DevelopmentArtifact>{ DevelopmentArtifact.universal },
+    DevelopmentArtifact.universal,
   );
 
   static const List<String> artifactNames = <String>[
@@ -991,8 +1095,10 @@
   ];
 
   @override
-  String get dyLdLibPath {
-    return cache.getArtifactDirectory(name).path;
+  Map<String, String> get environment {
+    return <String, String>{
+      'DYLD_LIBRARY_PATH': cache.getArtifactDirectory(name).path,
+    };
   }
 
   @override
@@ -1100,6 +1206,12 @@
   <String>['android-arm64-release/windows-x64', 'android-arm64-release/windows-x64.zip'],
 ];
 
+const List<List<String>> _iosBinaryDirs = <List<String>>[
+  <String>['ios', 'ios/artifacts.zip'],
+  <String>['ios-profile', 'ios-profile/artifacts.zip'],
+  <String>['ios-release', 'ios-release/artifacts.zip'],
+];
+
 const List<List<String>> _androidBinaryDirs = <List<String>>[
   <String>['android-x86', 'android-x86/artifacts.zip'],
   <String>['android-x64', 'android-x64/artifacts.zip'],
@@ -1111,12 +1223,6 @@
   <String>['android-arm64-release', 'android-arm64-release/artifacts.zip'],
 ];
 
-const List<List<String>> _iosBinaryDirs = <List<String>>[
-  <String>['ios', 'ios/artifacts.zip'],
-  <String>['ios-profile', 'ios-profile/artifacts.zip'],
-  <String>['ios-release', 'ios-release/artifacts.zip'],
-];
-
 const List<List<String>> _dartSdks = <List<String>> [
   <String>['darwin-x64', 'dart-sdk-darwin-x64.zip'],
   <String>['linux-x64', 'dart-sdk-linux-x64.zip'],
diff --git a/packages/flutter_tools/lib/src/commands/build_aar.dart b/packages/flutter_tools/lib/src/commands/build_aar.dart
index a7ef675..fd9eb3a 100644
--- a/packages/flutter_tools/lib/src/commands/build_aar.dart
+++ b/packages/flutter_tools/lib/src/commands/build_aar.dart
@@ -9,7 +9,7 @@
 import '../build_info.dart';
 import '../project.dart';
 import '../reporting/reporting.dart';
-import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
+import '../runner/flutter_command.dart' show FlutterCommandResult;
 import 'build.dart';
 
 class BuildAarCommand extends BuildSubCommand {
@@ -53,12 +53,6 @@
   }
 
   @override
-  Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
-    DevelopmentArtifact.universal,
-    DevelopmentArtifact.android,
-  };
-
-  @override
   final String description = 'Build a repository containing an AAR and a POM file.\n\n'
       'The POM file is used to include the dependencies that the AAR was compiled against.\n\n'
       'To learn more about how to use these artifacts, see '
diff --git a/packages/flutter_tools/lib/src/commands/build_apk.dart b/packages/flutter_tools/lib/src/commands/build_apk.dart
index 8936f0f..507ed5a 100644
--- a/packages/flutter_tools/lib/src/commands/build_apk.dart
+++ b/packages/flutter_tools/lib/src/commands/build_apk.dart
@@ -10,7 +10,7 @@
 import '../globals.dart';
 import '../project.dart';
 import '../reporting/reporting.dart';
-import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
+import '../runner/flutter_command.dart' show FlutterCommandResult;
 import 'build.dart';
 
 class BuildApkCommand extends BuildSubCommand {
@@ -70,12 +70,6 @@
   }
 
   @override
-  Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
-    DevelopmentArtifact.universal,
-    DevelopmentArtifact.android,
-  };
-
-  @override
   Future<FlutterCommandResult> runCommand() async {
     final BuildInfo buildInfo = getBuildInfo();
     final AndroidBuildInfo androidBuildInfo = AndroidBuildInfo(
diff --git a/packages/flutter_tools/lib/src/commands/doctor.dart b/packages/flutter_tools/lib/src/commands/doctor.dart
index b2d2b51..7a3f06e 100644
--- a/packages/flutter_tools/lib/src/commands/doctor.dart
+++ b/packages/flutter_tools/lib/src/commands/doctor.dart
@@ -37,7 +37,7 @@
       // This is required because we use gen_snapshot to check if the host
       // machine can execute the provided artifacts. See `_genSnapshotRuns`
       // in `doctor.dart`.
-      DevelopmentArtifact.android,
+      DevelopmentArtifact.androidGenSnapshot,
     };
   }
 
diff --git a/packages/flutter_tools/lib/src/commands/precache.dart b/packages/flutter_tools/lib/src/commands/precache.dart
index 473fea5..e7f7103 100644
--- a/packages/flutter_tools/lib/src/commands/precache.dart
+++ b/packages/flutter_tools/lib/src/commands/precache.dart
@@ -10,13 +10,23 @@
 import '../version.dart';
 
 class PrecacheCommand extends FlutterCommand {
-  PrecacheCommand() {
+  PrecacheCommand({bool verboseHelp = false}) {
     argParser.addFlag('all-platforms', abbr: 'a', negatable: false,
         help: 'Precache artifacts for all host platforms.');
     argParser.addFlag('force', abbr: 'f', negatable: false,
         help: 'Force downloading of artifacts.');
     argParser.addFlag('android', negatable: true, defaultsTo: true,
-        help: 'Precache artifacts for Android development.');
+        help: 'Precache artifacts for Android development.',
+        hide: verboseHelp);
+    argParser.addFlag('android_gen_snapshot', negatable: true, defaultsTo: true,
+        help: 'Precache gen_snapshot for Android development.',
+        hide: !verboseHelp);
+    argParser.addFlag('android_maven', negatable: true, defaultsTo: true,
+        help: 'Precache Gradle dependencies for Android development.',
+        hide: !verboseHelp);
+    argParser.addFlag('android_internal_build', negatable: true, defaultsTo: false,
+        help: 'Precache dependencies for internal Android development.',
+        hide: !verboseHelp);
     argParser.addFlag('ios', negatable: true, defaultsTo: true,
         help: 'Precache artifacts for iOS development.');
     argParser.addFlag('web', negatable: true, defaultsTo: false,
@@ -58,6 +68,10 @@
       if (argResults[artifact.name]) {
         requiredArtifacts.add(artifact);
       }
+      // The `android` flag expands to android_gen_snapshot, android_maven, android_internal_build.
+      if (artifact.name.startsWith('android_') && argResults['android']) {
+        requiredArtifacts.add(artifact);
+      }
     }
     final bool forceUpdate = argResults['force'];
     if (forceUpdate || !cache.isUpToDate()) {
diff --git a/packages/flutter_tools/lib/src/commands/upgrade.dart b/packages/flutter_tools/lib/src/commands/upgrade.dart
index cd31444..23e885f 100644
--- a/packages/flutter_tools/lib/src/commands/upgrade.dart
+++ b/packages/flutter_tools/lib/src/commands/upgrade.dart
@@ -10,6 +10,7 @@
 import '../base/file_system.dart';
 import '../base/io.dart';
 import '../base/os.dart';
+import '../base/platform.dart';
 import '../base/process.dart';
 import '../cache.dart';
 import '../dart/pub.dart';
@@ -130,6 +131,7 @@
       ],
       workingDirectory: Cache.flutterRoot,
       allowReentrantFlutter: true,
+      environment: platform.environment,
     );
     if (code != 0) {
       throwToolExit(null, exitCode: code);
@@ -248,6 +250,7 @@
       ],
       workingDirectory: Cache.flutterRoot,
       allowReentrantFlutter: true,
+      environment: platform.environment,
     );
     if (code != 0) {
       throwToolExit(null, exitCode: code);
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index 597143e..e26ecb3 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -676,7 +676,7 @@
     case TargetPlatform.android_arm64:
     case TargetPlatform.android_x64:
     case TargetPlatform.android_x86:
-      return DevelopmentArtifact.android;
+      return DevelopmentArtifact.androidGenSnapshot;
     case TargetPlatform.web_javascript:
       return DevelopmentArtifact.web;
     case TargetPlatform.ios:
diff --git a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl
index 44f4667..926beae 100644
--- a/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl
+++ b/packages/flutter_tools/templates/module/android/host_app_common/app.tmpl/build.gradle.tmpl
@@ -47,9 +47,9 @@
     androidTestImplementation 'androidx.test.espresso:espresso-core:3.1.1'
     {{/androidX}}
     {{^androidX}}
-    implementation 'com.android.support:appcompat-v7:27.1.1'
+    implementation 'com.android.support:appcompat-v7:28.0.0'
     implementation 'com.android.support.constraint:constraint-layout:1.1.2'
-    implementation 'com.android.support:design:27.1.1'
+    implementation 'com.android.support:design:28.0.0'
     androidTestImplementation 'com.android.support.test:runner:1.0.2'
     androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
     {{/androidX}}
diff --git a/packages/flutter_tools/test/general.shard/cache_test.dart b/packages/flutter_tools/test/general.shard/cache_test.dart
index aa9d940..5a15cb7 100644
--- a/packages/flutter_tools/test/general.shard/cache_test.dart
+++ b/packages/flutter_tools/test/general.shard/cache_test.dart
@@ -8,8 +8,11 @@
 import 'package:meta/meta.dart';
 import 'package:mockito/mockito.dart';
 import 'package:platform/platform.dart';
+import 'package:process/process.dart';
 
+import 'package:flutter_tools/src/android/gradle.dart';
 import 'package:flutter_tools/src/base/common.dart';
+import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart' show InternetAddress, SocketException;
@@ -136,18 +139,30 @@
       when(artifact1.isUpToDate()).thenReturn(true);
       when(artifact2.isUpToDate()).thenReturn(false);
       final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2]);
-      await cache.updateAll(<DevelopmentArtifact>{});
-      verifyNever(artifact1.update(<DevelopmentArtifact>{}));
-      verify(artifact2.update(<DevelopmentArtifact>{}));
+      await cache.updateAll(<DevelopmentArtifact>{
+        null,
+      });
+      verifyNever(artifact1.update());
+      verify(artifact2.update());
     });
     testUsingContext('getter dyLdLibEntry concatenates the output of each artifact\'s dyLdLibEntry getter', () async {
-      final CachedArtifact artifact1 = MockCachedArtifact();
-      final CachedArtifact artifact2 = MockCachedArtifact();
-      final CachedArtifact artifact3 = MockCachedArtifact();
-      when(artifact1.dyLdLibPath).thenReturn('/path/to/alpha:/path/to/beta');
-      when(artifact2.dyLdLibPath).thenReturn('/path/to/gamma:/path/to/delta:/path/to/epsilon');
-      when(artifact3.dyLdLibPath).thenReturn(''); // Empty output
+      final IosUsbArtifacts artifact1 = MockIosUsbArtifacts();
+      final IosUsbArtifacts artifact2 = MockIosUsbArtifacts();
+      final IosUsbArtifacts artifact3 = MockIosUsbArtifacts();
+      when(artifact1.environment)
+          .thenReturn(<String, String>{
+            'DYLD_LIBRARY_PATH': '/path/to/alpha:/path/to/beta',
+          });
+      when(artifact2.environment)
+          .thenReturn(<String, String>{
+            'DYLD_LIBRARY_PATH': '/path/to/gamma:/path/to/delta:/path/to/epsilon',
+          });
+      when(artifact3.environment)
+          .thenReturn(<String, String>{
+            'DYLD_LIBRARY_PATH': '',
+          });
       final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2, artifact3]);
+
       expect(cache.dyLdLibEntry.key, 'DYLD_LIBRARY_PATH');
       expect(
         cache.dyLdLibEntry.value,
@@ -163,18 +178,20 @@
       when(artifact2.isUpToDate()).thenReturn(false);
       final MockInternetAddress address = MockInternetAddress();
       when(address.host).thenReturn('storage.googleapis.com');
-      when(artifact1.update(<DevelopmentArtifact>{})).thenThrow(SocketException(
+      when(artifact1.update()).thenThrow(SocketException(
         'Connection reset by peer',
         address: address,
       ));
       final Cache cache = Cache(artifacts: <CachedArtifact>[artifact1, artifact2]);
       try {
-        await cache.updateAll(<DevelopmentArtifact>{});
+        await cache.updateAll(<DevelopmentArtifact>{
+          null,
+        });
         fail('Mock thrown exception expected');
       } catch (e) {
-        verify(artifact1.update(<DevelopmentArtifact>{}));
+        verify(artifact1.update());
         // Don't continue when retrieval fails.
-        verifyNever(artifact2.update(<DevelopmentArtifact>{}));
+        verifyNever(artifact2.update());
         expect(
           testLogger.errorText,
           contains('https://flutter.dev/community/china'),
@@ -226,6 +243,7 @@
         binaryDirs: <List<String>>[
           <String>['bin_dir', 'unused_url_path'],
         ],
+        requiredArtifacts: DevelopmentArtifact.universal,
       );
       await artifact.updateInner();
       final Directory dir = memoryFileSystem.systemTempDirectory
@@ -244,25 +262,53 @@
     });
   });
 
-  testUsingContext('throws tool exit on fs exception', () async {
-    final FakeCachedArtifact fakeCachedArtifact = FakeCachedArtifact(
-      cache: MockCache(),
-      requiredArtifacts: <DevelopmentArtifact>{
-        DevelopmentArtifact.android,
-      }
-    );
-    final Directory mockDirectory = MockDirectory();
-    when(fakeCachedArtifact.cache.getArtifactDirectory(any))
-        .thenReturn(mockDirectory);
-    when(mockDirectory.existsSync()).thenReturn(false);
-    when(mockDirectory.createSync(recursive: true))
-        .thenThrow(const FileSystemException());
+  group('AndroidMavenArtifacts', () {
+    MemoryFileSystem memoryFileSystem;
+    MockProcessManager processManager;
+    MockCache mockCache;
 
-    expect(() => fakeCachedArtifact.update(<DevelopmentArtifact>{
-        DevelopmentArtifact.android,
-    }), throwsA(isInstanceOf<ToolExit>()));
-  }, overrides: <Type, Generator>{
-    FileSystem: () => MemoryFileSystem(),
+    setUp(() {
+      memoryFileSystem = MemoryFileSystem();
+      processManager = MockProcessManager();
+      mockCache = MockCache();
+    });
+
+    test('development artifact', () async {
+      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts();
+      expect(mavenArtifacts.developmentArtifact, DevelopmentArtifact.androidMaven);
+    });
+
+    testUsingContext('update', () async {
+      final AndroidMavenArtifacts mavenArtifacts = AndroidMavenArtifacts();
+      expect(mavenArtifacts.isUpToDate(), isFalse);
+
+      final Directory gradleWrapperDir = fs.systemTempDirectory.createTempSync('gradle_wrapper.');
+      when(mockCache.getArtifactDirectory('gradle_wrapper')).thenReturn(gradleWrapperDir);
+
+      fs.directory(gradleWrapperDir.childDirectory('gradle').childDirectory('wrapper'))
+          .createSync(recursive: true);
+      fs.file(fs.path.join(gradleWrapperDir.path, 'gradlew')).writeAsStringSync('irrelevant');
+      fs.file(fs.path.join(gradleWrapperDir.path, 'gradlew.bat')).writeAsStringSync('irrelevant');
+
+      when(processManager.run(any, environment: captureAnyNamed('environment')))
+        .thenAnswer((Invocation invocation) {
+          final List<String> args = invocation.positionalArguments[0];
+          expect(args.length, 6);
+          expect(args[1], '-b');
+          expect(args[2].endsWith('resolve_dependencies.gradle'), isTrue);
+          expect(args[5], 'resolveDependencies');
+          expect(invocation.namedArguments[#environment], gradleEnv);
+          return Future<ProcessResult>.value(ProcessResult(0, 0, '', ''));
+        });
+
+      await mavenArtifacts.update();
+
+      expect(mavenArtifacts.isUpToDate(), isFalse);
+    }, overrides: <Type, Generator>{
+      Cache: ()=> mockCache,
+      FileSystem: () => memoryFileSystem,
+      ProcessManager: () => processManager,
+    });
   });
 }
 
@@ -270,7 +316,7 @@
   FakeCachedArtifact({
     String stampName = 'STAMP',
     @required Cache cache,
-    Set<DevelopmentArtifact> requiredArtifacts = const <DevelopmentArtifact>{},
+    DevelopmentArtifact requiredArtifacts,
     this.binaryDirs = const <List<String>>[],
     this.licenseDirs = const <String>[],
     this.packageDirs = const <String>[],
@@ -290,12 +336,14 @@
   List<String> getPackageDirs() => packageDirs;
 }
 
+class MockProcessManager extends Mock implements ProcessManager {}
 class MockFileSystem extends Mock implements FileSystem {}
 class MockFile extends Mock implements File {}
 class MockDirectory extends Mock implements Directory {}
 
 class MockRandomAccessFile extends Mock implements RandomAccessFile {}
 class MockCachedArtifact extends Mock implements CachedArtifact {}
+class MockIosUsbArtifacts extends Mock implements IosUsbArtifacts {}
 class MockInternetAddress extends Mock implements InternetAddress {}
 class MockCache extends Mock implements Cache {}
 class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {}
diff --git a/packages/flutter_tools/test/general.shard/commands/precache_test.dart b/packages/flutter_tools/test/general.shard/commands/precache_test.dart
index 466fe51..d9241c9 100644
--- a/packages/flutter_tools/test/general.shard/commands/precache_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/precache_test.dart
@@ -27,12 +27,24 @@
       final PrecacheCommand command = PrecacheCommand();
       applyMocksToCommand(command);
       await createTestCommandRunner(command).run(
-        const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia', '--flutter_runner']
+        const <String>[
+          'precache',
+          '--ios',
+          '--android',
+          '--web',
+          '--macos',
+          '--linux',
+          '--windows',
+          '--fuchsia',
+          '--flutter_runner',
+        ]
       );
       expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
         DevelopmentArtifact.universal,
         DevelopmentArtifact.iOS,
-        DevelopmentArtifact.android,
+        DevelopmentArtifact.androidGenSnapshot,
+        DevelopmentArtifact.androidMaven,
+        DevelopmentArtifact.androidInternalBuild,
         DevelopmentArtifact.web,
         DevelopmentArtifact.macOS,
         DevelopmentArtifact.linux,
@@ -44,6 +56,54 @@
       Cache: () => cache,
     });
 
+    testUsingContext('Expands android artifacts when the android flag is used', () async {
+      // Release lock between test cases.
+      Cache.releaseLockEarly();
+
+      final PrecacheCommand command = PrecacheCommand();
+      applyMocksToCommand(command);
+      await createTestCommandRunner(command).run(
+        const <String>[
+          'precache',
+          '--no-ios',
+          '--android',
+        ]
+      );
+      expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.androidGenSnapshot,
+        DevelopmentArtifact.androidMaven,
+        DevelopmentArtifact.androidInternalBuild,
+      }));
+    }, overrides: <Type, Generator>{
+      Cache: () => cache,
+    });
+
+    testUsingContext('Adds artifact flags to requested android artifacts', () async {
+      // Release lock between test cases.
+      Cache.releaseLockEarly();
+
+      final PrecacheCommand command = PrecacheCommand();
+      applyMocksToCommand(command);
+      await createTestCommandRunner(command).run(
+        const <String>[
+          'precache',
+          '--no-ios',
+          '--android_gen_snapshot',
+          '--android_maven',
+          '--android_internal_build',
+        ]
+      );
+      expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.androidGenSnapshot,
+        DevelopmentArtifact.androidMaven,
+        DevelopmentArtifact.androidInternalBuild,
+      }));
+    }, overrides: <Type, Generator>{
+      Cache: () => cache,
+    });
+
     final MockFlutterVersion flutterVersion = MockFlutterVersion();
     when(flutterVersion.isMaster).thenReturn(false);
 
@@ -53,18 +113,31 @@
       final PrecacheCommand command = PrecacheCommand();
       applyMocksToCommand(command);
       await createTestCommandRunner(command).run(
-       const <String>['precache', '--ios', '--android', '--web', '--macos', '--linux', '--windows', '--fuchsia', '--flutter_runner']
+        const <String>[
+         'precache',
+         '--ios',
+         '--android_gen_snapshot',
+         '--android_maven',
+         '--android_internal_build',
+         '--web',
+         '--macos',
+         '--linux',
+         '--windows',
+         '--fuchsia',
+         '--flutter_runner',
+        ]
       );
-     expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
-       DevelopmentArtifact.universal,
-       DevelopmentArtifact.iOS,
-       DevelopmentArtifact.android,
-     }));
+      expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.iOS,
+        DevelopmentArtifact.androidGenSnapshot,
+        DevelopmentArtifact.androidMaven,
+        DevelopmentArtifact.androidInternalBuild,
+      }));
     }, overrides: <Type, Generator>{
       Cache: () => cache,
       FlutterVersion: () => flutterVersion,
     });
-
     testUsingContext('Downloads artifacts when --force is provided', () async {
       when(cache.isUpToDate()).thenReturn(true);
       // Release lock between test cases.
@@ -73,10 +146,12 @@
       applyMocksToCommand(command);
       await createTestCommandRunner(command).run(const <String>['precache', '--force']);
       expect(artifacts, unorderedEquals(<DevelopmentArtifact>{
-       DevelopmentArtifact.universal,
-       DevelopmentArtifact.iOS,
-       DevelopmentArtifact.android,
-     }));
+        DevelopmentArtifact.universal,
+        DevelopmentArtifact.iOS,
+        DevelopmentArtifact.androidGenSnapshot,
+        DevelopmentArtifact.androidMaven,
+        DevelopmentArtifact.androidInternalBuild,
+      }));
     }, overrides: <Type, Generator>{
       Cache: () => cache,
       FlutterVersion: () => flutterVersion,
diff --git a/packages/flutter_tools/test/general.shard/commands/run_test.dart b/packages/flutter_tools/test/general.shard/commands/run_test.dart
index b743a36..4285535 100644
--- a/packages/flutter_tools/test/general.shard/commands/run_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/run_test.dart
@@ -138,7 +138,7 @@
 
       expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
         DevelopmentArtifact.universal,
-        DevelopmentArtifact.android,
+        DevelopmentArtifact.androidGenSnapshot,
       }));
 
       when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
@@ -162,7 +162,7 @@
       expect(await RunCommand().requiredArtifacts, unorderedEquals(<DevelopmentArtifact>{
         DevelopmentArtifact.universal,
         DevelopmentArtifact.iOS,
-        DevelopmentArtifact.android,
+        DevelopmentArtifact.androidGenSnapshot,
       }));
 
       when(mockDeviceManager.getDevices()).thenAnswer((Invocation invocation) {
diff --git a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
index 94cefa6..306d8a0 100644
--- a/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
+++ b/packages/flutter_tools/test/general.shard/commands/upgrade_test.dart
@@ -5,12 +5,14 @@
 import 'package:flutter_tools/src/base/common.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
+import 'package:flutter_tools/src/base/platform.dart';
 import 'package:flutter_tools/src/base/os.dart';
 import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/commands/upgrade.dart';
 import 'package:flutter_tools/src/runner/flutter_command.dart';
 import 'package:flutter_tools/src/version.dart';
 import 'package:mockito/mockito.dart';
+import 'package:platform/platform.dart';
 import 'package:process/process.dart';
 
 import '../../src/common.dart';
@@ -22,6 +24,7 @@
     FakeUpgradeCommandRunner fakeCommandRunner;
     UpgradeCommandRunner realCommandRunner;
     MockProcessManager processManager;
+    FakePlatform fakePlatform;
     final MockFlutterVersion flutterVersion = MockFlutterVersion();
     const GitTagVersion gitTagVersion = GitTagVersion(1, 2, 3, 4, 5, 'asd');
     when(flutterVersion.channel).thenReturn('dev');
@@ -43,9 +46,13 @@
         return Future<Process>.value(createMockProcess());
       });
       fakeCommandRunner.willHaveUncomittedChanges = false;
+      fakePlatform = FakePlatform()..environment = <String, String>{
+        'ENV1': 'irrelevant',
+        'ENV2': 'irrelevant',
+      };
     });
 
-    test('throws on unknown tag, official branch,  noforce', () async {
+    testUsingContext('throws on unknown tag, official branch,  noforce', () async {
       final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
         false,
         false,
@@ -53,6 +60,8 @@
         flutterVersion,
       );
       expect(result, throwsA(isInstanceOf<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      Platform: () => fakePlatform,
     });
 
     testUsingContext('does not throw on unknown tag, official branch, force', () async {
@@ -65,9 +74,10 @@
       expect(await result, null);
     }, overrides: <Type, Generator>{
       ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
     });
 
-    test('throws tool exit with uncommitted changes', () async {
+    testUsingContext('throws tool exit with uncommitted changes', () async {
       fakeCommandRunner.willHaveUncomittedChanges = true;
       final Future<FlutterCommandResult> result = fakeCommandRunner.runCommand(
         false,
@@ -76,6 +86,8 @@
         flutterVersion,
       );
       expect(result, throwsA(isA<ToolExit>()));
+    }, overrides: <Type, Generator>{
+      Platform: () => fakePlatform,
     });
 
     testUsingContext('does not throw tool exit with uncommitted changes and force', () async {
@@ -90,6 +102,7 @@
       expect(await result, null);
     }, overrides: <Type, Generator>{
       ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
     });
 
     testUsingContext('Doesn\'t throw on known tag, dev branch, no force', () async {
@@ -102,6 +115,7 @@
       expect(await result, null);
     }, overrides: <Type, Generator>{
       ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
     });
 
     testUsingContext('verifyUpstreamConfigured', () async {
@@ -116,6 +130,57 @@
       await realCommandRunner.verifyUpstreamConfigured();
     }, overrides: <Type, Generator>{
       ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
+    });
+
+    testUsingContext('flutterUpgradeContinue passes env variables to child process', () async {
+      await realCommandRunner.flutterUpgradeContinue();
+
+      final VerificationResult result = verify(processManager.start(
+        <String>[
+          fs.path.join('bin', 'flutter'),
+          'upgrade',
+          '--continue',
+          '--no-version-check',
+        ],
+        environment: captureAnyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'),
+      ));
+
+      expect(result.captured.first, fakePlatform.environment);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
+    });
+
+    testUsingContext('precacheArtifacts passes env variables to child process', () async {
+      final List<String> precacheCommand = <String>[
+        fs.path.join('bin', 'flutter'),
+        '--no-color',
+        '--no-version-check',
+        'precache',
+      ];
+
+      when(processManager.start(
+        precacheCommand,
+        environment: anyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'),
+      )).thenAnswer((Invocation invocation) async {
+        return Future<Process>.value(createMockProcess());
+      });
+
+      await realCommandRunner.precacheArtifacts();
+
+      final VerificationResult result = verify(processManager.start(
+        precacheCommand,
+        environment: captureAnyNamed('environment'),
+        workingDirectory: anyNamed('workingDirectory'),
+      ));
+
+      expect(result.captured.first, fakePlatform.environment);
+    }, overrides: <Type, Generator>{
+      ProcessManager: () => processManager,
+      Platform: () => fakePlatform,
     });
   });