mobile/android/app/build.gradle
author Nick Alexander <nalexander@mozilla.com>
Tue, 18 Jul 2017 09:46:09 -0700
changeset 371819 5b150868209b49cb505d11a5ec8cacc67f90542e
parent 370160 aa10d14239126225de73ea948a0cefe71e4f283a
child 372942 514cf1f55c6fa80deb7490d80a59313577d625cc
permissions -rw-r--r--
Bug 1255227 - Part 1: Stop using deprecated android-sdk-manager Gradle plugin. r=sebastian This was only ever used to automatically fetch Android SDK dependencies in the android-gradle-dependencies job in Task Cluster. That function is now provided by newer Android-Gradle build plugins. MozReview-Commit-ID: Adrxm2rAPlZ

buildDir "${topobjdir}/gradle/build/mobile/android/app"

apply plugin: 'com.android.application'
apply plugin: 'checkstyle'
apply plugin: 'com.getkeepsafe.dexcount'
apply plugin: 'findbugs'

dexcount {
    format = "tree"
}

android {
    compileSdkVersion project.ext.compileSdkVersion
    buildToolsVersion project.ext.buildToolsVersion

    defaultConfig {
        targetSdkVersion project.ext.targetSdkVersion
        minSdkVersion project.ext.minSdkVersion
        manifestPlaceholders = project.ext.manifestPlaceholders

        applicationId mozconfig.substs.ANDROID_PACKAGE_NAME
        testApplicationId 'org.mozilla.roboexample.test'
        testInstrumentationRunner 'org.mozilla.gecko.FennecInstrumentationTestRunner'
        // Used by Robolectric based tests; see TestRunner.
        buildConfigField 'String', 'BUILD_DIR', "\"${project.buildDir}\""

        vectorDrawables.useSupportLibrary = true
    }

    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    dexOptions {
        javaMaxHeapSize "2g"
        jumboMode = true
    }

    lintOptions {
        abortOnError true
    }

    buildTypes {
        // We have the following difficult situation.  Minification (Proguard) is only available per
        // Android-Gradle `buildType`.  Instrumentation (Robocop) is only available for exactly one
        // `buildType` (see Android-Gradle `testBuildType`, which defaults to "debug").  Local
        // developers expect to build and run tests against the "debug" build type.  Automation
        // needs to produce an instrumentation (Robocop) APK against a Fennec APK that will ship.
        // (This is very unusual; usually, instrumentation tests do _not_ run against a shipping
        // APK.)
        //
        // Given these constraints, we should not change `testBuildType` to avoid confusing local
        // developers.  Also, we should not Proguard any "debug" builds, because we don't want local
        // developers to incur the cost of Proguard.  However, we still need to find a way to
        // Proguard a shipping APK and produce an instrumentation (Robocop APK) against it.  To
        // achieve this, we make "debug" builds Proguard in automation alone.  This does have the
        // unfortunate side effect of Proguarding the instrumentation (Robocop) APK, but nothing
        // uses runtime inspection or class-loading with that APK, so it shouldn't be a problem.
        def configureMinifyClosure = {
            // Bug 1229269: we can't yet shrinkResources effectively.  Be sure
            // to use -stripped.ap_ after enabling this.
            // shrinkResources true
            minifyEnabled true
            proguardFile "${topsrcdir}/mobile/android/config/proguard/proguard.cfg"
        }
        release configureMinifyClosure
        if (mozconfig.substs.MOZILLA_OFFICIAL) {
            debug configureMinifyClosure
        }
    }

    // The "audience" flavour dimension distinguishes between _local_ builds (intended for
    // development) and _official_ builds (intended for testing in automation and to ship in one of
    // the Fennec distribution channels).
    //
    // The "skin" flavor dimension distinguishes between different user interfaces.  We sometimes
    // want to develop significant new user interface pieces in-tree that don't ship (even in the
    // Nightly channel) while under development.  A new "skin" flavour allows us to develop such
    // pieces in Gradle without changing the mainline configuration.
    flavorDimensions "audience", "skin"

    productFlavors {
        // For API 21+ - with pre-dexing, this will be faster for local development.
        local {
            dimension "audience"

            // For pre-dexing, setting `minSdkVersion 21` allows the Android gradle plugin to
            // pre-DEX each module and produce an APK that can be tested on
            // Android Lollipop without time consuming DEX merging processes.
            minSdkVersion 21
            dexOptions {
                preDexLibraries true
            }
        }
        // For API < 21 - does not support pre-dexing because local development
        // is slow in that case.
        localOld {
            dimension "audience"
        }

        // Automation builds.  We use "official" rather than "automation" to drive these builds down
        // the list of configurations that Android Studio offers, thereby making it _not_ the
        // default.  This avoids a common issue with "omni.ja" not being packed into the default APK
        // built and deployed by Android Studio.
        official {
             dimension "audience"
        }

        // The default user interface.
        australis {
            dimension "skin"
        }

        // A new user interface, currently under development.
        photon {
            dimension "skin"
        }
    }

    sourceSets {
        main {
            manifest.srcFile "${project.buildDir}/generated/source/preprocessed_manifest/AndroidManifest.xml"

            aidl {
                srcDir "${topsrcdir}/mobile/android/base/aidl"
            }

            java {
                srcDir "${topsrcdir}/mobile/android/base/java"
                srcDir "${topsrcdir}/mobile/android/search/java"
                srcDir "${topsrcdir}/mobile/android/services/src/main/java"

                if (mozconfig.substs.MOZ_ANDROID_MLS_STUMBLER) {
                    srcDir "${topsrcdir}/mobile/android/stumbler/java"
                }

                if (!mozconfig.substs.MOZ_CRASHREPORTER) {
                    exclude 'org/mozilla/gecko/CrashReporter.java'
                }

                if (!mozconfig.substs.MOZ_NATIVE_DEVICES) {
                    exclude 'org/mozilla/gecko/ChromeCastDisplay.java'
                    exclude 'org/mozilla/gecko/ChromeCastPlayer.java'
                    exclude 'org/mozilla/gecko/GeckoMediaPlayer.java'
                    exclude 'org/mozilla/gecko/GeckoPresentationDisplay.java'
                    exclude 'org/mozilla/gecko/MediaPlayerManager.java'
                }

                if (mozconfig.substs.MOZ_INSTALL_TRACKING) {
                    exclude 'org/mozilla/gecko/adjust/StubAdjustHelper.java'
                } else {
                    exclude 'org/mozilla/gecko/adjust/AdjustHelper.java'
                }

                if (mozconfig.substs.MOZ_ANDROID_MMA) {
                    exclude 'org/mozilla/gecko/mma/MmaStubImp.java'
                } else {
                    exclude 'org/mozilla/gecko/mma/MmaLeanplumImp.java'
                }

                if (!mozconfig.substs.MOZ_ANDROID_GCM) {
                    exclude 'org/mozilla/gecko/gcm/**/*.java'
                    exclude 'org/mozilla/gecko/push/**/*.java'
                }

                srcDir "${project.buildDir}/generated/source/preprocessed_code" // See syncPreprocessedCode.
            }

            res {
                srcDir "${topsrcdir}/${mozconfig.substs.MOZ_BRANDING_DIRECTORY}/res"
                srcDir "${project.buildDir}/generated/source/preprocessed_resources" // See syncPreprocessedResources.
                srcDir "${topsrcdir}/mobile/android/services/src/main/res"
                if (mozconfig.substs.MOZ_CRASHREPORTER) {
                    srcDir "${topsrcdir}/mobile/android/base/crashreporter/res"
                }
            }

            assets {
                if (mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY && !mozconfig.substs.MOZ_ANDROID_PACKAGE_INSTALL_BOUNCER) {
                    // If we are packaging the bouncer, it will have the distribution, so don't put
                    // it in the main APK as well.
                    srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets"
                }
                srcDir "${topsrcdir}/mobile/android/app/assets"
            }
        }

        test {
            java {
                srcDir "${topsrcdir}/mobile/android/tests/background/junit4/src"

                if (!mozconfig.substs.MOZ_ANDROID_GCM) {
                    exclude 'org/mozilla/gecko/gcm/**/*.java'
                    exclude 'org/mozilla/gecko/push/**/*.java'
                }
            }
            resources {
                srcDir "${topsrcdir}/mobile/android/tests/background/junit4/resources"
            }
        }

        androidTest {
            java {
                srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/src"
                srcDir "${topsrcdir}/mobile/android/tests/background/junit3/src"
                srcDir "${topsrcdir}/mobile/android/tests/browser/junit3/src"
                srcDir "${topsrcdir}/mobile/android/tests/javaddons/src"
            }
            res {
                srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/res"
            }
            assets {
                srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/assets"
            }
        }
    }

    testOptions {
        unitTests.all {
            // We'd like to use (Runtime.runtime.availableProcessors()/2), but
            // we have tests that start test servers and the bound ports
            // collide.  We'll fix this soon to have much faster test cycles.
            maxParallelForks 1
        }
    }
}

dependencies {
    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
    compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"

    if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
        compile "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
        compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
        compile "com.google.android.gms:play-services-cast:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
    }

    if (mozconfig.substs.MOZ_INSTALL_TRACKING) {
        compile "com.google.android.gms:play-services-ads:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
    }

    if (mozconfig.substs.MOZ_ANDROID_GCM) {
        compile "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
        compile "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
        compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
        compile "com.google.android.gms:play-services-measurement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
    }

    // Include LeakCanary in most gradle based builds. LeakCanary adds about 5k methods, so we disable
    // it for the (non-proguarded, non-predex) localOld builds to allow space for other libraries.
    // Gradle based tests include the no-op version.  Mach based builds only include the no-op version
    // of this library.
    // It doesn't seem like there is a non-trivial way to be conditional on 'localOld', so instead we explicitly
    // define a version of leakcanary for every flavor:
    localCompile 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
    localOldCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
    officialCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
    officialCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
    testCompile 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'

    compile project(':geckoview')
    compile project(':thirdparty')

    testCompile 'junit:junit:4.12'
    testCompile 'org.robolectric:robolectric:3.1.2'
    testCompile 'org.simpleframework:simple-http:6.0.1'
    testCompile 'org.mockito:mockito-core:1.10.19'

    // Including the Robotium JAR directly can cause issues with dexing.
    androidTestCompile 'com.jayway.android.robotium:robotium-solo:5.5.4'
}

// TODO: (bug 1261486): This impl is not robust -
// we just wanted to land something.
task checkstyle(type: Checkstyle) {
    configFile file("checkstyle.xml")
    // TODO: should use sourceSets from project instead of hard-coded str.
    source = ['../base/java/','../geckoview/src/main/java/']
    // TODO: This ignores our pre-processed resources.
    include '**/*.java'
    // TODO: classpath should probably be something.
    classpath = files()
}

task syncPreprocessedCode(type: Sync, dependsOn: rootProject.generateCodeAndResources) {
    into("${project.buildDir}/generated/source/preprocessed_code")
    from("${topobjdir}/mobile/android/base/generated/preprocessed") {
        // All other preprocessed code is included in the geckoview project.
        include '**/AdjustConstants.java'
        include '**/MmaConstants.java'
    }
}

// The localization system uses the moz.build preprocessor to interpolate a .dtd
// file of XML entity definitions into an XML file of elements referencing those
// entities.  (Each locale produces its own .dtd file, backstopped by the en-US
// .dtd file in tree.)  Android Studio (and IntelliJ) don't handle these inline
// entities smoothly.  This filter merely expands the entities in place, making
// them appear properly throughout the IDE.  Be aware that this assumes that the
// JVM's file.encoding is utf-8.  See comments in
// mobile/android/mach_commands.py.
class ExpandXMLEntitiesFilter extends FilterReader {
    ExpandXMLEntitiesFilter(Reader input) {
        // Extremely inefficient, but whatever.
        super(new StringReader(groovy.xml.XmlUtil.serialize(new XmlParser(false, false, true).parse(input))))
    }
}

task syncPreprocessedResources(type: Sync, dependsOn: rootProject.generateCodeAndResources) {
    into("${project.buildDir}/generated/source/preprocessed_resources")
    from("${topobjdir}/mobile/android/base/res")
    filesMatching('**/strings.xml') {
        filter(ExpandXMLEntitiesFilter)
    }
}

// It's not easy -- see the backout in Bug 1242213 -- to change the <manifest>
// package for Fennec.  Gradle has grown a mechanism to achieve what we want for
// Fennec, however, with applicationId.  To use the same manifest as moz.build,
// we replace the package with org.mozilla.gecko (the eventual package) here.
task rewriteManifestPackage(type: Copy, dependsOn: rootProject.generateCodeAndResources) {
    into("${project.buildDir}/generated/source/preprocessed_manifest")
    from("${topobjdir}/mobile/android/base/AndroidManifest.xml")
    filter { it.replaceFirst(/package=".*?"/, 'package="org.mozilla.gecko"') }
}

apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"

android.applicationVariants.all { variant ->
    variant.preBuild.dependsOn rewriteManifestPackage
    variant.preBuild.dependsOn syncPreprocessedCode
    variant.preBuild.dependsOn syncPreprocessedResources

    // Official automation builds don't include Gecko binaries, since those binaries are not
    // produced until after build time (at package time).  official Therefore, automation builds
    // include the Gecko binaries into the APK at package time.  The "withGeckoBinaries" variant of
    // the :geckoview project also does this.  (It does what it says on the tin!)  For notes on this
    // approach, see mobile/android/gradle/with_gecko_binaries.gradle.

    // Like 'local' or 'localOld'.
    def audienceDimension = variant.productFlavors[0].name

    // :app uses :geckoview:release and handles it's own Gecko binary inclusion,
    // even though this would be most naturally done in the :geckoview project.
    if (!audienceDimension.equals('official')) {
        configureVariantWithGeckoBinaries(variant)
    }
}

apply plugin: 'spoon'

spoon {
    // For now, let's be verbose.
    debug = true
    // It's not helpful to pass when we don't have a device connected.
    failIfNoDeviceConnected = true

    def spoonPackageName
    if (gradle.startParameter.taskNames.contains('runBrowserTests')) {
        spoonPackageName = 'org.mozilla.tests.browser.junit3'
    }
    if (gradle.startParameter.taskNames.contains('runBackgroundTests')) {
        spoonPackageName = 'org.mozilla.gecko.background'
    }
    if (project.hasProperty('spoonPackageName')) {
        // Command line overrides everything.
        spoonPackageName = project.spoonPackageName
    }
    if (spoonPackageName) {
        instrumentationArgs = ['-e', "package=${spoonPackageName}".toString()]
    }
}

// // See discussion at https://github.com/stanfy/spoon-gradle-plugin/issues/9.
// afterEvaluate {
//     tasks["spoonLocal${android.testBuildType.capitalize()}AndroidTest"].outputs.upToDateWhen { false }

//     // This is an awkward way to define different sets of instrumentation tests.
//     // The task name itself is fished at runtime and the package name configured
//     // in the spoon configuration.
//     task runBrowserTests {
//         dependsOn tasks["spoonLocalOldDebugAndroidTest"]
//     }
//     task runBackgroundTests {
//         dependsOn tasks["spoonLocalOldDebugAndroidTest"]
//     }
// }

// Bug 1299015: Complain to treeherder if checkstyle, lint, or unittest fails.  It's not obvious
// how to listen to individual errors in most cases, so we just link to the reports for now.
def makeTaskExecutionListener(artifactRootUrl) {
    return new TaskExecutionListener() {
        void beforeExecute(Task task) {
            // Do nothing.
        }

        void afterExecute(Task task, TaskState state) {
            if (!state.failure) {
                return
            }

            // Link to the failing report.  The task path and the report path
            // depend on the android-lint task in
            // taskcluster/ci/android-stuff/kind.yml.  It's not possible to link
            // directly, so for now consumers will need to copy-paste the URL.
            switch (task.path) {
            case ':app:checkstyle':
                def url = "${artifactRootUrl}/public/android/checkstyle/checkstyle.xml"
                println "TEST-UNEXPECTED-FAIL | android-checkstyle | Checkstyle rule violations were found. See the report at: $url"
                break

            case ':app:lintOfficialAustralisDebug':
                def url = "${artifactRootUrl}/public/android/lint/lint-results-officialAustralisDebug.html"
                println "TEST-UNEXPECTED-FAIL | android-lint | Lint found errors in the project; aborting build. See the report at: $url"
                break

            case ':app:testOfficialAustralisDebugUnitTest':
                def url = "${artifactRootUrl}/public/android/unittest/officialAustralisDebug/index.html"
                println "TEST-UNEXPECTED-FAIL | android-test | There were failing tests. See the reports at: $url"
                break

            case ':app:findbugsHtmlOfficialAustralisDebug':
                def url = "${artifactRootUrl}/public/android/findbugs/findbugs-officialAustralisDebug-output.html"
                println "TEST-UNEXPECTED-FAIL | android-findbugs | Findbugs found issues in the project. See the report at: $url"
                break

            case ':app:findbugsXmlOfficialAustralisDebug':
                def url = "${artifactRootUrl}/public/android/findbugs/findbugs-officialAustralisDebug-output.xml"
                println "TEST-UNEXPECTED-FAIL | android-findbugs | Findbugs found issues in the project. See the report at: $url"
                break

            case ':app:lintOfficialPhotonDebug':
                def url = "${artifactRootUrl}/public/android/lint/lint-results-officialPhotonDebug.html"
                println "TEST-UNEXPECTED-FAIL | android-lint | Lint found errors in the project; aborting build. See the report at: $url"
                break

            case ':app:testOfficialPhotonDebugUnitTest':
                def url = "${artifactRootUrl}/public/android/unittest/officialPhotonDebug/index.html"
                println "TEST-UNEXPECTED-FAIL | android-test | There were failing tests. See the reports at: $url"
                break

            case ':app:findbugsHtmlOfficialPhotonDebug':
                def url = "${artifactRootUrl}/public/android/findbugs/findbugs-officialPhotonDebug-output.html"
                println "TEST-UNEXPECTED-FAIL | android-findbugs | Findbugs found issues in the project. See the report at: $url"
                break

            case ':app:findbugsXmlOfficialPhotonDebug':
                def url = "${artifactRootUrl}/public/android/findbugs/findbugs-officialPhotonDebug-output.xml"
                println "TEST-UNEXPECTED-FAIL | android-findbugs | Findbugs found issues in the project. See the report at: $url"
                break
            }
        }
    }
}

// TASK_ID and RUN_ID are provided by docker-worker; see
// https://docs.taskcluster.net/manual/execution/workers/docker-worker.
if (System.env.TASK_ID && System.env.RUN_ID) {
    def artifactRootUrl = "https://queue.taskcluster.net/v1/task/${System.env.TASK_ID}/runs/${System.env.RUN_ID}/artifacts"
    gradle.addListener(makeTaskExecutionListener(artifactRootUrl))
}

if (gradle.startParameter.taskNames.any { it.endsWith('UnitTest') }) {
    // Approach cribbed from https://github.com/rwinch/jce-checker.
    int maxKeyLen = javax.crypto.Cipher.getMaxAllowedKeyLength("AES")
    if (maxKeyLen <= 128) {
        throw new GradleException(
            "Android unit tests require " +
            "Java Cryptography Extension (JCE) Unlimited Strength Jurisdiction Policy, see " +
            "http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html")
    }
}

// Bug 1320035: Gradle configuration for running findbugs.  Findbugs only allows
// to generate one report per invocation: https://stackoverflow.com/a/42720235.
// Run two tasks, accepting the cost of duplicate work.
android.applicationVariants.all { variant ->
    task("findbugsHtml${variant.name.capitalize()}", type: FindBugs) {
        // TODO: figure out how to share the shared configuration.
        description "Analyze ${variant.name} code with findbugs (HTML report)"
        group "Verification"

        ignoreFailures = false // We want builds to fail when running this task and issues are found
        effort = "max"         // Using more memory and time to find issues is acceptable in automation
        reportLevel = "high"   // For now we only care about high priority bugs. After we have fixed
                               // the issues with medium/low priority we can lower the report level here.

        classes = files("$project.buildDir/intermediates/classes")
        source = variant.javaCompile.source
        classpath = variant.javaCompile.classpath

        excludeFilter = file("findbugs-exclude.xml")
        dependsOn "assemble${variant.name.capitalize()}"

        reports {
            html.enabled = true // HTML reports for humans.
            html.destination = "$project.buildDir/outputs/findbugs/findbugs-${variant.name}-output.html"
            xml.enabled = false
        }
    }

    task("findbugsXml${variant.name.capitalize()}", type: FindBugs) {
        // TODO: figure out how to share the shared configuration.
        description "Analyze ${variant.name} code with findbugs (XML report)"
        group "Verification"

        ignoreFailures = false // We want builds to fail when running this task and issues are found
        effort = "max"         // Using more memory and time to find issues is acceptable in automation
        reportLevel = "high"   // For now we only care about high priority bugs. After we have fixed
                               // the issues with medium/low priority we can lower the report level here.
        
        classes = files("$project.buildDir/intermediates/classes")
        source = variant.javaCompile.source
        classpath = variant.javaCompile.classpath

        excludeFilter = file("findbugs-exclude.xml")
        dependsOn "assemble${variant.name.capitalize()}"

        reports {
            xml.enabled = true // XML reports for machines.
            xml.destination = "$project.buildDir/outputs/findbugs/findbugs-${variant.name}-output.xml"
            html.enabled = false
        }
    }
}

// Bug 1353055 - Strip 'vars' debugging information to agree with moz.build.
apply from: "${topsrcdir}/mobile/android/gradle/debug_level.gradle"
android.applicationVariants.all configureVariantDebugLevel


// Bug 1320310 - Hack up the manifest produced by Gradle to match that produced
// by moz.build.  Per https://bugzilla.mozilla.org/show_bug.cgi?id=1320310#c14,
// this breaks launching in Android Studio; therefore, we only do this for
// official automation builds and not for local developer builds.
import groovy.xml.XmlUtil

android.applicationVariants.all { variant ->
    // Like 'local', 'localOld', or 'official'.
    def audienceDimension = variant.productFlavors[0].name
    if (!audienceDimension.equals('official')) {
        return
    }

    variant.outputs.each { output ->
        output.processManifest.doLast {
            [output.processManifest.manifestOutputFile,
             output.processManifest.instantRunManifestOutputFile,
            ].each({ File manifestOutFile ->
                if (manifestOutFile.exists()) {
                    def contents = manifestOutFile.getText('UTF-8')

                    // A non-validating, non-namespace aware XML processor.
                    def xml = new XmlSlurper(false, false).parseText(contents)

                    // First, reinstate our <activity-alias android:name=".App">.
                    xml.depthFirst()
                        .findAll { it.name() == 'activity-alias' && it.'@android:name' == 'org.mozilla.gecko.App' }
                        .each { it.'@android:name' = '.App' }

                    // Second, cull all manifest entries provided by com.google.android.gms.measurement.
                    xml.depthFirst()
                        .findAll { it.'@android:name'.text().contains('AppMeasurement') }
                        .each { it.replaceNode {} }

                    manifestOutFile.write(XmlUtil.serialize(xml), 'UTF-8')
                }
            })
        }
    }
}