Backed out 20 changesets (bug 1411654) for incorrect android:debuggable. r=nalexander, a=RyanVM
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 17 Jan 2018 15:55:38 -0500
changeset 451521 ef04f3ad847540c876c130b7d140a960d922a024
parent 451520 90814e6a3ebba31c858b36f8a780f4af24391d7f
child 451522 6fe09f2de63da649e9a8bbff701b445df7d75094
push id8554
push userryanvm@gmail.com
push dateWed, 17 Jan 2018 22:21:44 +0000
treeherdermozilla-beta@0c5a115449a3 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersnalexander, RyanVM
bugs1411654
milestone59.0a1
backs outcfad693be9185ae858a29a715b3e5a6d2d3d6fbf
55776829a744ec20be08d29083c4b39b9519b9b4
c5bf85d56fedf600363dda3b077fdf950ba60688
c270f97bb0da1347151bb429c81bba0044545da5
fde9bf9c14c31c11b4e9279ae7b56b94b5a02d53
01836fd98c6351667c70cfd187cf1e3c437e1f94
730a70767743b74a7e3a1fcf4018540edfdf30a3
690e265c684ce70ecb89355314fd1574bb421f0b
f918500d9cf5112b70bc8e0a120df435b02252b7
cec2b8828cc8800fa269d290ce38ea82c454b445
76085ddd5ac768b3ec8dfe9ca03283630c436681
2b37201606f5a921d91339e2c8a2ec86885b68e8
d0d513d1c3791cbb7ae24b22699f0663e45943d9
e7b0cc801cf1406a46f576106f014c74a90d9534
901b304603d9d4816856d560c61387501efceadc
373c9a71d9451498462594b302b4fe2648431fef
3dc3beab95f83b2f08ff9ff305fdd4b85cc05d9d
22a861db1573364916ab2c5b6d0c6321ba08ff55
0850b319efd43ac8f4d61485451722975da55ca1
d276d3deba053f66d74d258d964cab3fd5358a14
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 20 changesets (bug 1411654) for incorrect android:debuggable. r=nalexander, a=RyanVM Backed out changeset cfad693be918 (bug 1411654) Backed out changeset 55776829a744 (bug 1411654) Backed out changeset c5bf85d56fed (bug 1411654) Backed out changeset c270f97bb0da (bug 1411654) Backed out changeset fde9bf9c14c3 (bug 1411654) Backed out changeset 01836fd98c63 (bug 1411654) Backed out changeset 730a70767743 (bug 1411654) Backed out changeset 690e265c684c (bug 1411654) Backed out changeset f918500d9cf5 (bug 1411654) Backed out changeset cec2b8828cc8 (bug 1411654) Backed out changeset 76085ddd5ac7 (bug 1411654) Backed out changeset 2b37201606f5 (bug 1411654) Backed out changeset d0d513d1c379 (bug 1411654) Backed out changeset e7b0cc801cf1 (bug 1411654) Backed out changeset 901b304603d9 (bug 1411654) Backed out changeset 373c9a71d945 (bug 1411654) Backed out changeset 3dc3beab95f8 (bug 1411654) Backed out changeset 22a861db1573 (bug 1411654) Backed out changeset 0850b319efd4 (bug 1411654) Backed out changeset d276d3deba05 (bug 1411654)
build.gradle
build/docs/toolchains.rst
gradle.properties
gradle/wrapper/gradle-wrapper.properties
mobile/android/app/build.gradle
mobile/android/app/lint.xml
mobile/android/app/src/main/res/drawable/ic_as_trending.xml
mobile/android/app/src/main/res/layout/customtabs_action_bar_custom_view.xml
mobile/android/app/src/main/res/values-v11/themes.xml
mobile/android/app/src/main/res/values-v13/search_styles.xml
mobile/android/app/src/main/res/values-v16/search_styles.xml
mobile/android/app/src/main/res/values-v17/themes.xml
mobile/android/app/src/main/res/values/themes.xml
mobile/android/app/src/photon/res/values-v11/styles.xml
mobile/android/app/src/photon/res/values-v16/styles.xml
mobile/android/app/src/photon/res/values/styles.xml
mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
mobile/android/app/src/test/java/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
mobile/android/app/src/test/java/org/mozilla/gecko/icons/preparation/TestLookupIconUrl.java
mobile/android/app/src/test/java/org/mozilla/gecko/util/NetworkUtilsTest.java
mobile/android/app/src/test/resources/robolectric.properties
mobile/android/base/AndroidManifest.xml.in
mobile/android/base/Makefile.in
mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/Highlight.java
mobile/android/base/java/org/mozilla/gecko/customtabs/ActionBarPresenter.java
mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsSecurityPopup.java
mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java
mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java
mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java
mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java
mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
mobile/android/base/moz.build
mobile/android/base/resources/values-v17/themes.xml
mobile/android/docs/gradle.rst
mobile/android/docs/index.rst
mobile/android/geckoview/build.gradle
mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
mobile/android/geckoview_example/build.gradle
mobile/android/gradle.configure
mobile/android/gradle/debug_level.gradle
mobile/android/gradle/product_flavors.gradle
mobile/android/gradle/with_gecko_binaries.gradle
mobile/android/mach_commands.py
mobile/android/services/src/test/java/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/db/TestTabsProvider.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
mobile/android/services/src/test/java/org/mozilla/gecko/db/LocalBrowserDBTest.java
mobile/android/services/src/test/java/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
mobile/android/services/src/test/java/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
mobile/android/thirdparty/build.gradle
old-configure.in
python/mozboot/mozboot/android-packages.txt
python/mozbuild/mozbuild/config_status.py
taskcluster/ci/build/android.yml
taskcluster/scripts/misc/android-gradle-dependencies/after.sh
taskcluster/scripts/misc/android-gradle-dependencies/nexus.xml
--- a/build.gradle
+++ b/build.gradle
@@ -11,16 +11,17 @@ def tryInt = { string ->
 allprojects {
     // Expose the per-object-directory configuration to all projects.
     ext {
         mozconfig = gradle.mozconfig
         topsrcdir = gradle.mozconfig.topsrcdir
         topobjdir = gradle.mozconfig.topobjdir
 
         compileSdkVersion = tryInt(mozconfig.substs.ANDROID_COMPILE_SDK_VERSION)
+        buildToolsVersion = tryInt(mozconfig.substs.ANDROID_BUILD_TOOLS_VERSION)
         targetSdkVersion = tryInt(mozconfig.substs.ANDROID_TARGET_SDK)
         minSdkVersion = tryInt(mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION)
         manifestPlaceholders = [
             ANDROID_PACKAGE_NAME: mozconfig.substs.ANDROID_PACKAGE_NAME,
             ANDROID_TARGET_SDK: mozconfig.substs.ANDROID_TARGET_SDK,
             MOZ_ANDROID_MIN_SDK_VERSION: mozconfig.substs.MOZ_ANDROID_MIN_SDK_VERSION,
             MOZ_ANDROID_SHARED_ID: "${mozconfig.substs.ANDROID_PACKAGE_NAME}.sharedID",
         ]
@@ -46,17 +47,17 @@ buildscript {
         }
         // For in tree plugins.
         maven {
             url "file://${gradle.mozconfig.topsrcdir}/mobile/android/gradle/m2repo"
         }
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:3.0.0'
+        classpath 'com.android.tools.build:gradle:2.3.3'
         // Provided in tree.
         classpath 'com.getkeepsafe.dexcount:dexcount-gradle-plugin:0.7.3'
     }
 }
 
 if ('multi' == System.env.AB_CD) {
     // Multi-l10n builds set `AB_CD=multi`, which isn't a valid locale.  This
     // causes the
--- a/build/docs/toolchains.rst
+++ b/build/docs/toolchains.rst
@@ -65,43 +65,47 @@ an internal Mozilla server.  The build a
 verify, and extract these archive before building.  These archives
 provide a self-contained Gradle and Maven repository so that machines
 don't need to fetch additional Maven dependencies at build time.
 (Gradle and the downloaded Maven dependencies can be both
 redistributed publicly.)
 
 Archiving the Gradle executable is straight-forward, but archiving a
 local Maven repository is not.  Therefore a special Task Cluster
-Docker image and toolchain job exist for producing the required
-archives.  The Docker image definition is rooted in
-``taskcluster/docker/android-build``.  The Task Cluster toolchain job
-is named `android-gradle-dependencies`.  The job runs in a container
-based on the custom Docker image and spawns a Sonatype Nexus proxying
-Maven repository process in the background.  The job builds Firefox
-for Android using Gradle and the in-tree Gradle configuration rooted
-at ``build.gradle``.  The spawned proxying Maven repository downloads
-external dependencies and collects them.  After the Gradle build
-completes, the job archives the Gradle version used to build, and the
-downloaded Maven repository, and exposes them as Task Cluster
-artifacts.
+Docker image and job exist for producing the required archives.  The
+Docker image definition is rooted in
+``taskcluster/docker/android-build``.  The Task Cluster job
+definition is in
+``testing/taskcluster/tasks/builds/android_api_16_gradle_dependencies.yml``.
+The job runs in a container based on the custom Docker image and
+spawns a Sonatype Nexus proxying Maven repository process in the
+background.  The job builds Firefox for Android using Gradle and the
+in-tree Gradle configuration rooted at ``build.gradle``.  The spawned
+proxying Maven repository downloads external dependencies and collects
+them.  After the Gradle build completes, the job archives the Gradle
+version used to build, and the downloaded Maven repository, and
+exposes them as Task Cluster artifacts.
+
+Here is `an example try job fetching these dependencies
+<https://treeherder.mozilla.org/#/jobs?repo=try&revision=75bc98935147&selectedJob=17793653>`_.
+The resulting task produced a `Gradle archive
+<https://queue.taskcluster.net/v1/task/CeYMgAP3Q-KF8h37nMhJjg/runs/0/artifacts/public%2Fbuild%2Fgradle.tar.xz>`_
+and a `Maven repository archive
+<https://queue.taskcluster.net/v1/task/CeYMgAP3Q-KF8h37nMhJjg/runs/0/artifacts/public%2Fbuild%2Fjcentral.tar.xz>`_.
+These archives were then uploaded (manually) to Mozilla automation
+using tooltool for consumption in Gradle builds.
 
 To update the version of Gradle in the archive produced, update
 ``gradle/wrapper/gradle-wrapper.properties``.  Be sure to also update
 the SHA256 checksum to prevent poisoning the build machines!
 
 To update the versions of Gradle dependencies used, update
 ``dependencies`` sections in the in-tree Gradle configuration rooted
 at ``build.gradle``.  Once you are confident your changes build
-locally, push a fresh build to try.  The `android-gradle-dependencies`
-toolchain should run automatically, fetching your new dependencies and
-wiring them into the appropriate try build jobs.
+locally, push a fresh try build with an invocation like::
 
-To update the version of Sonatype Nexus, update `NEXUS_VERSION` in the
-`android-build` Docker image.
+   $ hg push-to-try -m "try: -b o -p android-api-16-gradle-dependencies"
 
-To modify the Sonatype Nexus configuration, typically to proxy a new
-remote Maven repository, modify
-`taskcluster/scripts/misc/android-gradle-dependencies/nexus.xml`.
-
-There is also a toolchain job that fetches the Android SDK and related
-packages.  To update the versions of packaged fetched, modify
-`python/mozboot/mozboot/android-packages.txt` and update the various
-in-tree versions accordingly.
+Then `upload your archives to tooltool
+<https://wiki.mozilla.org/ReleaseEngineering/Applications/Tooltool#How_To_Upload_To_Tooltool>`_,
+update the in-tree manifests in
+``mobile/android/config/tooltool-manifests``, and push a fresh try
+build.
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,4 +1,3 @@
 org.gradle.parallel=true
 org.gradle.daemon=true
 org.gradle.jvmargs=-Xmx2560M
-android.enableAapt2=false
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,7 +1,7 @@
 #Fri Sep 16 15:41:50 PDT 2016
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
-distributionSha256Sum=5c07b3bac2209fbc98fb1fdf6fd831f72429cdf8c503807404eae03d8c8099e5
+distributionUrl=https\://services.gradle.org/distributions/gradle-3.4.1-all.zip
+distributionSha256Sum=ed7e9c8bb41bd10d4c9339c95b2f8b122f5bf13188bd90504a26e0f00b123b0d
--- a/mobile/android/app/build.gradle
+++ b/mobile/android/app/build.gradle
@@ -1,23 +1,22 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/app"
 
 apply plugin: 'com.android.application'
 apply plugin: 'checkstyle'
 apply plugin: 'com.getkeepsafe.dexcount'
 apply plugin: 'findbugs'
 
-apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"
-
 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'
@@ -80,23 +79,45 @@ android {
     // 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.
-
-    project.configureProductFlavors.delegate = it
-    project.configureProductFlavors()
-
-    flavorDimensions "audience", "geckoBinaries", "minApi", "skin"
+    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"
+        }
+
         // Since Firefox 57, the mobile user interface has followed the Photon design.
         // Before Firefox 57, the user interface followed the Australis design.
         photon {
             dimension "skin"
         }
     }
 
     sourceSets {
@@ -136,33 +157,48 @@ android {
                 } 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) {
                     srcDir "${mozconfig.substs.MOZ_ANDROID_DISTRIBUTION_DIRECTORY}/assets"
                 }
             }
         }
 
+        // Every configuration needs the stub manifest at
+        // src/main/AndroidManifest.xml and the generated manifest.  We can't
+        // use the main sourceSet without losing the stub, so we cover all the
+        // configurations by enumerating the buildTypes here.
+        debug {
+            manifest.srcFile "${project.buildDir}/generated/source/preprocessed_manifest/AndroidManifest.xml"
+        }
+
+        release {
+            manifest.srcFile "${project.buildDir}/generated/source/preprocessed_manifest/AndroidManifest.xml"
+        }
+
         test {
             java {
                 // Bug 1229149 tracks pushing this into a :services Gradle project.
                 srcDir "${topsrcdir}/mobile/android/services/src/test/java"
 
                 if (!mozconfig.substs.MOZ_ANDROID_GCM) {
                     exclude 'org/mozilla/gecko/gcm/**/*.java'
                     exclude 'org/mozilla/gecko/push/**/*.java'
@@ -186,176 +222,169 @@ android {
             }
             assets {
                 srcDir "${topsrcdir}/mobile/android/tests/browser/robocop/assets"
             }
         }
     }
 
     testOptions {
-        // For Robolectric: see https://github.com/robolectric/robolectric/issues/3333#issuecomment-324300418.
-        unitTests.includeAndroidResources true
-
         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 {
-    implementation "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:cardview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:recyclerview-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:design:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:customtabs:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    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}"
 
     if (mozconfig.substs.MOZ_NATIVE_DEVICES) {
-        implementation "com.android.support:mediarouter-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-        implementation "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-cast:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        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) {
-        implementation "com.google.android.gms:play-services-analytics:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-analytics:${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) {
-        implementation "com.google.android.gms:play-services-basement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-base:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-measurement:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_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-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.  Gradle based tests
-    // include the no-op version.  Mach based builds only include the no-op
-    // version of this library.
-    debugImplementation 'com.squareup.leakcanary:leakcanary-android:1.4-beta1'
-    releaseImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
-    testImplementation 'com.squareup.leakcanary:leakcanary-android-no-op:1.4-beta1'
+    // 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'
 
-    implementation project(path: ':geckoview')
-    implementation project(path: ':thirdparty')
+    // With a simple "compile", Gradle will always build these libraries in their default configuration
+    // (i.e. release), so we need to explicitly forward our own build configuration here (bug 1385695).
+    //
+    // Official builds compile a hacked up app:Official..Debug, but need the
+    // release versions of the dependencies, in order to not have debugging
+    // information.  It's not yet possible to specify just officialDebug, so we
+    // hack around it here.
+    if (mozconfig.substs.MOZILLA_OFFICIAL) {
+        debugCompile project(path: ':geckoview', configuration: "release")
+    } else {
+        debugCompile project(path: ':geckoview', configuration: "debug")
+    }
+    releaseCompile project(path: ':geckoview', configuration: "release")
 
-    testImplementation 'junit:junit:4.12'
-    testImplementation 'org.robolectric:robolectric:3.5.1'
-    testImplementation 'org.simpleframework:simple-http:6.0.1'
-    testImplementation 'org.mockito:mockito-core:1.10.19'
+    if (mozconfig.substs.MOZILLA_OFFICIAL) {
+        debugCompile project(path: ':thirdparty', configuration: "release")
+    } else {
+        debugCompile project(path: ':thirdparty', configuration: "debug")
+    }
+    releaseCompile project(path: ':thirdparty', configuration: "release")
+
+    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.
-    androidTestImplementation 'com.jayway.android.robotium:robotium-solo:5.5.4'
+    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")
+}
+
 // 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 ->
-    def syncPreprocessedJava = task("syncPreprocessedJavaFor${variant.name.capitalize()}", type: Sync) {
-        into("${project.buildDir}/moz.build/src/${variant.name}/java")
-        from("${topobjdir}/mobile/android/base/generated/preprocessed")
-        exclude('**/*.mkdir.done')
-    }
-    // This is an Android-Gradle plugin 3+-ism.  Culted from reading the source,
-    // searching for "registerJavaGeneratingTask", and finding
-    // https://github.com/GoogleCloudPlatform/endpoints-framework-gradle-plugin/commit/2f2b91476fb1c6647791e2c6fe531a47615a1e85.
-    // The added directory doesn't appear in the paths listed by the
-    // `sourceSets` task, for reasons unknown.
-    variant.registerJavaGeneratingTask(syncPreprocessedJava, syncPreprocessedJava.destinationDir)
-
-    def syncPreprocessedRes = task("syncPreprocessedResFor${variant.name.capitalize()}", type: Sync) {
-        into("${project.buildDir}/moz.build/src/${variant.name}/res")
-        from("${topobjdir}/mobile/android/base/res")
-        filesMatching('**/strings.xml') {
-            filter(ExpandXMLEntitiesFilter)
-        }
-        exclude('**/*.mkdir.done')
-    }
-    // This is an Android-Gradle plugin 3+-ism.  Determined by reading the
-    // source.  The added directory doesn't appear in the paths listed by the
-    // `sourceSets` task, for reasons unknown.
-    variant.registerGeneratedResFolders(project.files(syncPreprocessedRes.destinationDir).builtBy(syncPreprocessedRes))
+    variant.preBuild.dependsOn rewriteManifestPackage
+    variant.preBuild.dependsOn syncPreprocessedCode
+    variant.preBuild.dependsOn syncPreprocessedResources
 
-    // 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.
-    def rewriteManifestPackage = task("rewriteManifestPackageFor${variant.name.capitalize()}", type: Copy, dependsOn: rootProject.generateCodeAndResources) {
-        into("${project.buildDir}/moz.build/src/${variant.name}")
-        from("${topobjdir}/mobile/android/base/AndroidManifest.xml")
-        filter { it.replaceFirst(/package=".*?"/, 'package="org.mozilla.gecko"') }
-        exclude('**/*.mkdir.done')
-    }
-
-    // Every configuration needs the stub manifest at
-    // src/main/AndroidManifest.xml and the generated manifest.  We can't use
-    // the main sourceSet without losing the stub, so we cover all the
-    // configurations here.
-    android.sourceSets."${variant.name}".manifest.srcFile "${rewriteManifestPackage.destinationDir}/AndroidManifest.xml"
-    variant.preBuild.dependsOn rewriteManifestPackage
-
+    // 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.
 
-    // Local (read, not 'official') builds want to reflect developer changes to
-    // AndroidManifest.xml.in, strings.xml, and preprocessed Java code.  To do
-    // this, the Gradle build calls out to the moz.build system, which can be
-    // re-entrant.  Official builds are driven by the moz.build system and
-    // should never be re-entrant in this way.
-    if (!((variant.productFlavors*.name).contains('official'))) {
-        syncPreprocessedJava.dependsOn rootProject.generateCodeAndResources
-        syncPreprocessedRes.dependsOn rootProject.generateCodeAndResources
-        rewriteManifestPackage.dependsOn rootProject.generateCodeAndResources
-    }
+    // Like 'local' or 'localOld'.
+    def audienceDimension = variant.productFlavors[0].name
 
-    // When driven from moz.build via |mach build|, Gradle does not require or
-    // use Gecko binaries.  It's only |mach package| that packs the Gecko
-    // binaries into the resulting APK.  The "withoutGeckoBinaries" variants
-    // handle this.  When driven from Android Studio or Gradle, the
-    // "withGeckoBinaries" variants handle packing the Gecko binaries into the
-    // resulting APK (for on-device deployment).  They also update the Omnijars
-    // as necessary, smoothing out the edit-compile-test development cycle.
-    // They do what they say on the tin!
-    if ((variant.productFlavors*.name).contains('withGeckoBinaries')) {
+    // :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)
     }
 }
 
 android.applicationVariants.all { variant ->
-    configureApplicationVariantWithJNIWrappers(variant, "Fennec")
+    configureVariantWithJNIWrappers(variant, "Fennec")
 }
 
 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 " +
@@ -382,17 +411,17 @@ android.applicationVariants.all { varian
         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 = file("$project.buildDir/reports/findbugs/findbugs-${variant.name}-output.html")
+            html.destination = "$project.buildDir/reports/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"
@@ -406,17 +435,17 @@ android.applicationVariants.all { varian
         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 = file("$project.buildDir/reports/findbugs/findbugs-${variant.name}-output.xml")
+            xml.destination = "$project.buildDir/reports/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
@@ -432,17 +461,18 @@ android.applicationVariants.all { varian
     // Like 'local', 'localOld', or 'official'.
     def audienceDimension = variant.productFlavors[0].name
     if (!audienceDimension.equals('official')) {
         return
     }
 
     variant.outputs.each { output ->
         output.processManifest.doLast {
-            [file("${manifestOutputDirectory}/AndroidManifest.xml"),
+            [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">.
--- a/mobile/android/app/lint.xml
+++ b/mobile/android/app/lint.xml
@@ -33,28 +33,30 @@
     <issue id="Instantiatable" severity="warning" />
     <issue id="LongLogTag" severity="warning" />
     <issue id="MissingPermission" severity="warning" />
     <issue id="OnClick" severity="warning" />
     <issue id="ReferenceType" severity="warning" />
     <issue id="ResourceAsColor" severity="warning" />
     <issue id="ResourceType" severity="warning" />
     <issue id="ValidFragment" severity="warning" />
+    <issue id="WrongConstant" severity="warning" />
 
     <!-- New Android-Gradle lint integration regressed this check. -->
     <issue id="MissingRegistered" severity="warning" />
 
     <!-- Fixes are in progress but we would like to block future candidates.
          The ** in the path are wildcards. We need these wildcards to not change our code structure.
          See: http://stackoverflow.com/questions/43994420/what-path-is-the-issue-ignore-path-element-in-lint-xml-relative-to -->
     <issue id="NewApi" severity="error">
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamPreference.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadContentTelemetry.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/preferences/LocaleListPreference.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/media/MediaControlService.java"/>
+        <ignore path="**/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/PrintHelper.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/animation/PropertyAnimator.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/RemotePresentationService.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/db/SharedBrowserDatabaseProvider.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/tabs/TabPanelBackButton.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/toolbar/ToolbarEditText.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/util/ViewUtil.java"/>
         <ignore path="**/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java"/>
--- a/mobile/android/app/src/main/res/drawable/ic_as_trending.xml
+++ b/mobile/android/app/src/main/res/drawable/ic_as_trending.xml
@@ -1,9 +1,9 @@
 <vector xmlns:android="http://schemas.android.com/apk/res/android"
         android:width="24dp"
         android:height="24dp"
         android:viewportWidth="12.0"
         android:viewportHeight="12.0">
     <path
         android:fillColor="@color/activity_stream_icon"
-        android:pathData="M4.97,0.151l-2.819,6.5A0.25,0.25,0,0,0,2.381,7H4.029a0.25,0.25,0,0,1,0.225,0.359L2,12,9.4,5.437A0.25,0.25,0,0,0,9.234,5H6.791a0.25,0.25,0,0,1,-0.19,-0.412L10.15,0.412A0.25,0.25,0,0,0,9.959,0H5.2A0.25,0.25,0,0,0,4.97,0.151Z"/>
+        android:pathData="M4.97.151l-2.819,6.5A.25.25,0,0,0,2.381,7H4.029a.25.25,0,0,1,.225.359L2,12,9.4,5.437A.25.25,0,0,0,9.234,5H6.791a.25.25,0,0,1-.19-.412L10.15.412A.25.25,0,0,0,9.959,0H5.2A.25.25,0,0,0,4.97.151Z"/>
 </vector>
--- a/mobile/android/app/src/main/res/layout/customtabs_action_bar_custom_view.xml
+++ b/mobile/android/app/src/main/res/layout/customtabs_action_bar_custom_view.xml
@@ -35,25 +35,26 @@
         android:animateLayoutChanges="true"
         android:gravity="center_vertical"
         android:orientation="vertical">
 
         <TextView
             android:id="@+id/custom_tabs_action_bar_title"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
-            android:singleLine="true"
+            android:lines="1"
             android:textColor="?android:textColorPrimary"
             android:textSize="14sp"
             tools:text="Mozilla.org"/>
 
         <TextView
             android:id="@+id/custom_tabs_action_bar_url"
             android:layout_width="match_parent"
             android:layout_height="wrap_content"
+            android:lines="1"
             android:singleLine="true"
             android:textColor="?android:textColorPrimary"
             android:textSize="10sp"
             android:ellipsize="middle"
             android:scrollHorizontally="true"
             tools:text="https://mozilla.org"/>
 
     </LinearLayout>
--- a/mobile/android/app/src/main/res/values-v11/themes.xml
+++ b/mobile/android/app/src/main/res/values-v11/themes.xml
@@ -10,16 +10,25 @@
         in other res/values-XXX/themes.xml.
     -->
     <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="android:windowContentOverlay">@null</item>
         <item name="windowActionBar">false</item>
         <item name="windowNoTitle">true</item>
     </style>
 
+    <style name="GeckoDialogBase" parent="@android:style/Theme.Holo.Light.Dialog">
+        <item name="android:windowContentOverlay">@null</item>
+        <item name="android:windowActionBar">false</item>
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowBackground">@android:color/transparent</item>
+    </style>
+
+    <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Holo.Light.Dialog" />
+
     <!--
         Activity based themes for API 11+. This theme completely replaces
         GeckoAppBase from res/values/themes.xml on API 11+ devices.
     -->
     <style name="GeckoAppBase" parent="Gecko">
         <item name="android:actionButtonStyle">@style/GeckoActionBar.Button</item>
         <item name="android:actionModeCopyDrawable">@drawable/ab_copy</item>
         <item name="android:actionModeCutDrawable">@drawable/ab_cut</item>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/main/res/values-v13/search_styles.xml
@@ -0,0 +1,15 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<resources>
+
+    <!-- Base application theme. -->
+    <style name="AppTheme" parent="@android:style/Theme.Holo.Light.NoActionBar">
+        <item name="android:windowBackground">@color/toolbar_grey</item>
+        <item name="android:colorBackground">@color/toolbar_grey</item>
+    </style>
+
+    <style name="SettingsTheme" parent="@android:style/Theme.Holo.Light"/>
+
+</resources>
new file mode 100644
--- /dev/null
+++ b/mobile/android/app/src/main/res/values-v16/search_styles.xml
@@ -0,0 +1,19 @@
+<!-- This Source Code Form is subject to the terms of the Mozilla Public
+   - License, v. 2.0. If a copy of the MPL was not distributed with this
+   - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
+
+<resources>
+
+    <style name="TextAppearance.EmptyView.Title" parent="@android:style/TextAppearance.Small">
+        <item name="android:textColor">@color/text_and_tabs_tray_grey</item>
+        <item name="android:textSize">20sp</item>
+        <item name="android:fontFamily">sans-serif-light</item>
+    </style>
+
+    <style name="TextAppearance.EmptyView.Message" parent="@android:style/TextAppearance.Small">
+        <item name="android:textColor">@color/placeholder_grey</item>
+        <item name="android:textSize">16sp</item>
+        <item name="android:lineSpacingExtra">4sp</item>
+    </style>
+
+</resources>
--- a/mobile/android/app/src/main/res/values/themes.xml
+++ b/mobile/android/app/src/main/res/values/themes.xml
@@ -1,26 +1,32 @@
 <?xml version="1.0" encoding="utf-8"?>
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<resources
-    xmlns:tools="http://schemas.android.com/tools">
+<resources>
 
     <!--
         Base application theme. This could be overridden by GeckoBaseTheme
         in other res/values-XXX/themes.xml.
     -->
     <style name="GeckoBase" parent="Theme.AppCompat.Light.DarkActionBar">
         <item name="windowNoTitle">true</item>
         <item name="windowActionBar">false</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
 
+    <style name="GeckoDialogBase" parent="@android:style/Theme.Dialog">
+        <item name="android:windowNoTitle">true</item>
+        <item name="android:windowContentOverlay">@null</item>
+    </style>
+
+    <style name="GeckoTitleDialogBase" parent="@android:style/Theme.Dialog" />
+
     <style name="Gecko.Preferences">
         <item name="windowActionBar">true</item>
         <item name="windowNoTitle">false</item>
         <item name="actionBarTheme">@style/ThemeOverlay.AppCompat.Dark.ActionBar</item>
         <item name="actionBarStyle">@style/PreferencesActionBar</item>
     </style>
 
     <!--
@@ -62,16 +68,20 @@
         <item name="android:textAppearanceMediumInverse">@style/TextAppearance.Medium.Inverse</item>
         <item name="android:textAppearanceSmallInverse">@style/TextAppearance.Small.Inverse</item>
 
         <item name="colorAccent">@color/action_accent</item>
 
         <item name="actionBarTheme">@style/GeckoActionBar</item>
     </style>
 
+    <style name="Gecko.Dialog" parent="GeckoDialogBase"/>
+
+    <style name="Gecko.TitleDialog" parent="GeckoTitleDialogBase"/>
+
     <!--
         Activity based themes, dependent on API level. This theme is replaced
         by GeckoAppBase from res/values-vXX/themes.xml on newer devices.
     -->
     <style name="GeckoAppBase" parent="Gecko">
         <item name="android:buttonStyle">@style/Widget.Button</item>
         <item name="android:dropDownItemStyle">@style/Widget.DropDownItem</item>
         <item name="android:editTextStyle">@style/Widget.EditText</item>
@@ -111,18 +121,17 @@
 
     <style name="GeckoCustomTabs" parent="Theme.AppCompat.Light.NoActionBar">
         <item name="menuItemActionBarStyle">@style/Widget.MenuItemActionBar</item>
         <item name="menuItemDefaultStyle">@style/Widget.MenuItemCustomTabs</item>
         <item name="windowActionModeOverlay">true</item>
     </style>
 
     <!-- Bookmark full-page dialog theme -->
-    <style name="Bookmark" parent="Theme.AppCompat.Light.DialogWhenLarge"
-           tools:ignore="UnusedResources" />
+    <style name="Bookmark" parent="Theme.AppCompat.Light.DialogWhenLarge"/>
     <style name="Bookmark.Gecko" parent="Gecko">
         <item name="toolbarStyle">@style/BookmarkToolbarStyle</item>
         <item name="colorAccent">@color/fennec_ui_accent</item>
         <item name="colorControlNormal">@color/disabled_grey</item>
 
         <item name="android:textColorHint">@color/tabs_tray_icon_grey</item>
     </style>
     <style name="BookmarkToolbarStyle.Base" parent="@style/Widget.AppCompat.Toolbar">
--- a/mobile/android/app/src/photon/res/values-v11/styles.xml
+++ b/mobile/android/app/src/photon/res/values-v11/styles.xml
@@ -1,16 +1,15 @@
 <?xml version="1.0" encoding="utf-8"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
-<resources xmlns:android="http://schemas.android.com/apk/res/android"
-           xmlns:tools="http://schemas.android.com/tools">
+<resources xmlns:android="http://schemas.android.com/apk/res/android">
 
     <!--
         Only overriden styles for Honeycomb/Ice cream sandwich are specified here.
         Please refer to values/styles.xml for default styles.
     -->
 
     <!--
         Base application styles. This could be overridden in other res/values-XXX/themes.xml.
@@ -22,16 +21,19 @@
     <style name="Widget.BaseEditText" parent="android:style/Widget.Holo.Light.EditText" />
 
     <style name="Widget.BaseListView" parent="android:style/Widget.Holo.ListView" />
 
     <style name="Widget.BaseGridView" parent="android:style/Widget.Holo.GridView" />
 
     <style name="Widget.BaseTextView" parent="android:style/Widget.Holo.Light.TextView" />
 
+    <style name="Widget.ProgressBar.Horizontal" parent="android:style/Widget.Holo.ProgressBar.Horizontal" />
+
+
     <!--
         Application styles. All customizations that are not specific
         to a particular API level can go here.
     -->
     <style name="Widget.ListItem">
         <item name="android:textColor">@color/select_item_multichoice</item>
         <item name="android:minHeight">?android:attr/listPreferredItemHeight</item>
         <item name="android:textAppearance">?android:attr/textAppearanceLarge</item>
@@ -40,16 +42,28 @@
         <item name="android:paddingRight">7dip</item>
         <item name="android:checkMark">?android:attr/listChoiceIndicatorMultiple</item>
         <item name="android:ellipsize">marquee</item>
     </style>
 
     <!-- ActionBar -->
     <style name="ActionBar" parent="android:style/Widget.Holo.ActionBar" />
 
+    <!-- TabsLayout ActionBar -->
+    <style name="ActionBar.TabsLayout">
+        <item name="android:visibility">gone</item>
+    </style>
+
+    <!-- DropDown List View -->
+    <style name="DropDownListView" parent="@android:style/Widget.Holo.ListView.DropDown">
+        <item name="android:listSelector">@drawable/action_bar_button</item>
+        <item name="android:divider">@color/toolbar_divider_grey</item>
+        <item name="android:dividerHeight">@dimen/page_row_divider_height</item>
+    </style>
+
     <!-- Spinner DropDown Item -->
     <style name="Widget.DropDownItem.Spinner" parent="@android:style/Widget.Holo.Light.DropDownItem.Spinner">
         <item name="android:textColor">#FF000000</item>
     </style>
 
     <style name="Widget.Spinner" parent="android:style/Widget.Holo.Light.Spinner">
         <item name="android:minWidth">@dimen/doorhanger_input_width</item>
     </style>
@@ -89,15 +103,14 @@
         <item name="android:layout_marginTop">16dp</item>
         <item name="android:layout_marginBottom">16dp</item>
     </style>
 
     <style name="TabInput"></style>
 
     <style name="TabInput.TabWidget" parent="android:style/Widget.Holo.Light.TabWidget" />
 
-    <style name="TabInput.Tab" parent="android:style/Widget.Holo.Light.Tab"
-           tools:ignore="UnusedResources">
+    <style name="TabInput.Tab" parent="android:style/Widget.Holo.Light.Tab">
         <item name="android:minHeight">@dimen/menu_item_row_height</item>
         <item name="android:textAllCaps">true</item>
     </style>
 
 </resources>
--- a/mobile/android/app/src/photon/res/values-v16/styles.xml
+++ b/mobile/android/app/src/photon/res/values-v16/styles.xml
@@ -9,16 +9,24 @@
     <style name="TextAppearance.EmptyMessage" parent="TextAppearance.Large">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 
     <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance.Medium">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 
+    <style name="TextAppearance.FirstrunTextLight">
+        <item name="android:fontFamily">sans-serif-light</item>
+    </style>
+
+    <style name="TextAppearance.FirstrunTextRegular">
+        <item name="android:fontFamily">sans-serif</item>
+    </style>
+
     <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small">
         <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TextAppearance.SearchSuggestion" parent="TextAppearance.Small">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 </resources>
--- a/mobile/android/app/src/photon/res/values/styles.xml
+++ b/mobile/android/app/src/photon/res/values/styles.xml
@@ -20,16 +20,18 @@
     <style name="Widget.BaseEditText" parent="android:style/Widget.EditText"/>
 
     <style name="Widget.BaseListView" parent="android:style/Widget.ListView"/>
 
     <style name="Widget.BaseGridView" parent="android:style/Widget.GridView"/>
 
     <style name="Widget.BaseTextView" parent="android:style/Widget.TextView"/>
 
+    <style name="Widget.ProgressBar.Horizontal" parent="android:style/Widget.ProgressBar.Horizontal"/>
+
     <!--
         Application styles. All customizations that are not specific
         to a particular API level can go here.
     -->
     <style name="Widget.Button" parent="Widget.BaseButton">
         <item name="android:textAppearance">@style/TextAppearance.Widget.Button</item>
     </style>
 
@@ -58,16 +60,20 @@
 
     <style name="Widget.GridView" parent="Widget.BaseGridView">
         <item name="android:verticalSpacing">0dip</item>
         <item name="android:horizontalSpacing">0dip</item>
         <item name="android:cacheColorHint">@android:color/transparent</item>
         <item name="android:listSelector">@drawable/action_bar_button</item>
     </style>
 
+    <style name="Widget.Home.HomeList">
+        <item name="android:scrollbarStyle">outsideOverlay</item>
+    </style>
+
     <style name="Widget.ListItem">
         <item name="android:minHeight">?android:attr/listPreferredItemHeight</item>
         <item name="android:textAppearance">?android:attr/textAppearanceLargeInverse</item>
         <item name="android:gravity">center_vertical</item>
         <item name="android:paddingLeft">12dip</item>
         <item name="android:paddingStart">12dip</item>
         <item name="android:paddingRight">7dip</item>
         <item name="android:paddingEnd">7dip</item>
@@ -205,16 +211,22 @@
 
     <style name="Widget.BookmarksListView" parent="Widget.HomeListView"/>
 
     <style name="Widget.TopSitesThumbnailView">
       <item name="android:padding">0dip</item>
       <item name="android:scaleType">centerCrop</item>
     </style>
 
+    <style name="Widget.TopSitesGridItemPin">
+      <item name="android:minWidth">30dip</item>
+      <item name="android:minHeight">30dip</item>
+      <item name="android:padding">0dip</item>
+    </style>
+
     <style name="Widget.TopSitesGridItemTitle">
       <item name="android:textColor">@color/top_sites_grid_item_title</item>
       <item name="android:textSize">12sp</item>
       <item name="android:paddingTop">5dip</item>
       <item name="android:gravity">left|start</item>
     </style>
 
     <style name="Widget.HomeListView" parent="Widget.ListView">
@@ -278,18 +290,17 @@
         <item name="android:layout_weight">1</item>
         <item name="android:minHeight">48dp</item>
         <item name="android:textSize">14sp</item>
     </style>
 
     <!--
         We are overriding the snackbar message style to guarantee a consistent style across Android versions (bug 1217416).
     -->
-    <style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance" tools:override="true"
-           tools:ignore="UnusedResources">
+    <style name="TextAppearance.Design.Snackbar.Message" parent="android:TextAppearance" tools:override="true">
         <item name="android:textSize">@dimen/design_snackbar_text_size</item>
         <item name="android:textColor">@android:color/white</item>
     </style>
 
     <!--
         TextAppearance
         Note: Gecko uses light theme as default, while Android uses dark.
         If Android convention has to be followd, the list of colors specified 
@@ -354,18 +365,17 @@
         <item name="android:textStyle">italic</item>
     </style>
 
     <style name="TextAppearance.Micro">
         <item name="android:textSize">12sp</item>
         <item name="android:textColor">?android:attr/textColorTertiary</item>
     </style>
 
-    <style name="TextAppearance.Micro.Inverse"
-           tools:ignore="UnusedResources">
+    <style name="TextAppearance.Micro.Inverse">
         <item name="android:textColor">?android:attr/textColorTertiaryInverse</item>
         <item name="android:textColorHint">?android:attr/textColorHintInverse</item>
         <item name="android:textColorHighlight">@color/text_color_highlight_inverse</item>
         <item name="android:textColorLink">?android:attr/textColorLink</item>
     </style>
 
     <style name="TextAppearance.Widget" />
 
@@ -385,18 +395,17 @@
         <item name="android:textColor">@color/primary_text</item>
     </style>
 
     <style name="TextAppearance.Widget.HomePagerTabMenuStrip" parent="TextAppearance.Small">
         <item name="android:textColor">?android:attr/textColorHint</item>
         <item name="android:textSize">14sp</item>
     </style>
 
-    <style name="TextAppearance.Widget.Home"
-           tools:ignore="UnusedResources" />
+    <style name="TextAppearance.Widget.Home" />
 
     <style name="TextAppearance.Widget.Home.Header" parent="TextAppearance.Small">
         <item name="android:textColor">@color/disabled_grey</item>
         <item name="android:textSize">12sp</item>
     </style>
 
     <style name="TextAppearance.Widget.Home.ItemTitle" parent="TextAppearance">
         <item name="android:textSize">16sp</item>
@@ -422,18 +431,17 @@
     <style name="TextAppearance.DoorHanger.Medium.Bold">
         <item name="android:fontFamily">sans-serif-medium</item>
     </style>
 
     <style name="TextAppearance.DoorHanger.Medium.Light">
         <item name="android:fontFamily">sans-serif-light</item>
     </style>
 
-    <style name="TextAppearance.DoorHanger.Small"
-           tools:ignore="UnusedResources">
+    <style name="TextAppearance.DoorHanger.Small">
         <item name="android:textSize">14sp</item>
     </style>
 
     <style name="TextAppearance.UrlBar.Title" parent="TextAppearance.Small">
         <item name="android:textSize">16sp</item>
     </style>
 
     <style name="TextAppearance.SearchSuggestion" parent="TextAppearance.Small"/>
@@ -514,31 +522,40 @@
         <item name="android:layout_marginRight">4dp</item>
     </style>
 
     <style name="TabsListItemFavicon">
         <item name="android:layout_marginLeft">10dp</item>
         <item name="android:layout_marginRight">6dp</item>
     </style>
 
+    <style name="Widget.RemoteTabsItemView" parent="Widget.TwoLinePageRow"/>
+
     <style name="Widget.RemoteTabsClientView" parent="Widget.TwoLinePageRow">
         <item name="android:background">@color/about_page_header_grey</item>
     </style>
 
+    <style name="Widget.RemoteTabsListView" parent="Widget.HomeListView">
+        <item name="android:childDivider">@color/toolbar_divider_grey</item>
+    </style>
+
+    <style name="Widget.HistoryListView" parent="Widget.HomeListView">
+        <item name="android:childDivider">@color/toolbar_divider_grey</item>
+        <item name="android:drawSelectorOnTop">true</item>
+    </style>
+
     <!-- TabsLayout Row -->
-    <style name="TabLayoutItemTextAppearance"
-           tools:ignore="UnusedResources">
+    <style name="TabLayoutItemTextAppearance">
         <item name="android:textColor">#FFFFFFFF</item>
         <item name="android:singleLine">true</item>
         <item name="android:ellipsize">middle</item>
     </style>
 
     <!-- TabsLayout RemoteTabs Row Url -->
-    <style name="TabLayoutItemTextAppearance.Url"
-           tools:ignore="UnusedResources">
+    <style name="TabLayoutItemTextAppearance.Url">
         <item name="android:textColor">#FFA4A7A9</item>
     </style>
 
     <!-- Find bar -->
     <style name="FindBar">
         <item name="android:background">@color/text_and_tabs_tray_grey</item>
         <item name="android:paddingLeft">3dip</item>
         <item name="android:paddingStart">3dip</item>
@@ -557,18 +574,18 @@
         <item name="android:layout_marginRight">5dip</item>
         <item name="android:layout_marginEnd">5dip</item>
         <item name="android:scaleType">fitCenter</item>
         <item name="android:layout_centerVertical">true</item>
         <item name="android:background">@drawable/action_bar_button_inverse</item>
     </style>
 
     <style name="PopupAnimation">
-        <item name="android:windowEnterAnimation">@anim/popup_show</item>
-        <item name="android:windowExitAnimation">@anim/popup_hide</item>
+        <item name="@android:windowEnterAnimation">@anim/popup_show</item>
+        <item name="@android:windowExitAnimation">@anim/popup_hide</item>
     </style>
 
     <style name="ToastBase">
         <item name="android:background">@drawable/toast_background</item>
         <item name="android:layout_width">match_parent</item>
         <item name="android:layout_height">wrap_content</item>
         <item name="android:layout_alignParentBottom">true</item>
         <item name="android:layout_centerHorizontal">true</item>
@@ -715,18 +732,17 @@
 
     <style name="TabInput"></style>
 
     <style name="TabInput.TabWidget">
         <item name="android:divider">@drawable/divider_vertical</item>
         <item name="android:background">@drawable/tab_indicator_background</item>
     </style>
 
-    <style name="TabInput.Tab"
-           tools:ignore="UnusedResources">
+    <style name="TabInput.Tab">
         <item name="android:background">@drawable/tabs_strip_indicator</item>
         <item name="android:gravity">center</item>
         <item name="android:minHeight">@dimen/menu_item_row_height</item>
     </style>
 
     <style name="TextAppearance.FirstrunLight"/>
     <style name="TextAppearance.FirstrunRegular"/>
 
@@ -777,18 +793,17 @@
     </style>
 
     <style name="RemoteTabsPanelItem.TextAppearance.Header">
         <item name="android:textColor">@color/placeholder_active_grey</item>
         <item name="android:textSize">20sp</item>
         <item name="android:layout_marginBottom">8dp</item>
     </style>
 
-    <style name="RemoteTabsPanelItem.TextAppearance.Linkified"
-           tools:ignore="UnusedResources">
+    <style name="RemoteTabsPanelItem.TextAppearance.Linkified">
         <item name="android:clickable">true</item>
         <item name="android:focusable">true</item>
         <item name="android:textColor">#0092DB</item>
     </style>
 
     <style name="RemoteTabsPanelItem.Button">
         <item name="android:background">@drawable/remote_tabs_setup_button_background</item>
         <item name="android:textColor">#FFFFFF</item>
@@ -800,27 +815,28 @@
         <item name="android:paddingStart">8dp</item>
         <item name="android:paddingRight">8dp</item>
         <item name="android:paddingEnd">8dp</item>
 
         <!-- AppCompat sets Button text to all caps so we override that here. -->
         <item name="textAllCaps">false</item>
     </style>
 
+    <style name="TabQueueActivity" parent="android:style/Theme.NoDisplay" />
+
     <style name="ActivityStreamContextMenuText">
         <item name="android:textSize">16sp</item>
     </style>
 
     <!-- We use this style to provide our own divider that has an inset on the left side -->
     <style name="ActivityStreamContextMenuStyle">
         <item name="android:listDivider">@drawable/as_contextmenu_divider</item>
     </style>
 
-    <style name="ActivityStreamButton" parent="Widget.AppCompat.Button.Colored"
-           tools:ignore="UnusedResources">
+    <style name="ActivityStreamButton" parent="Widget.AppCompat.Button.Colored">
         <item name="colorButtonNormal">@color/link_blue</item>
         <item name="android:textColor">@android:color/white</item>
     </style>
 
     <!-- centerInside will downscale larger icons to fit or center smaller favicons and allow us to draw a colored
          border around them. When changing these values, consider favicons downloaded from the internet and
          those provided by suggested sites, including suggested sites from distributions. -->
     <style name="ActivityStreamFaviconView">
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/GlobalPageMetadataTest.java
@@ -1,39 +1,42 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.os.RemoteException;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
 import org.mozilla.gecko.db.BrowserContract.PageMetadata;
 import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.db.LocalBrowserDB;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import static org.junit.Assert.*;
 
 @RunWith(TestRunner.class)
 public class GlobalPageMetadataTest {
     @Test
     public void testQueueing() throws Exception {
         BrowserDB db = new LocalBrowserDB("default");
 
-        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        BrowserProvider provider = new BrowserProvider();
         try {
+            provider.onCreate();
+            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+
             ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient pageMetadataClient = cr.acquireContentProviderClient(PageMetadata.CONTENT_URI);
 
             assertEquals(0, GlobalPageMetadata.getInstance().getMetadataQueueSize());
 
             // There's not history record for this uri, so test that queueing works.
             GlobalPageMetadata.getInstance().doAddOrQueue(db, pageMetadataClient, "https://mozilla.org", false, "{type: 'article'}");
 
@@ -56,18 +59,21 @@ public class GlobalPageMetadataTest {
 
     @Test
     public void testInsertingMetadata() throws Exception {
         BrowserDB db = new LocalBrowserDB("default");
 
         // Start listening for events.
         GlobalPageMetadata.getInstance().init();
 
-        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        BrowserProvider provider = new BrowserProvider();
         try {
+            provider.onCreate();
+            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+
             ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient historyClient = cr.acquireContentProviderClient(BrowserContract.History.CONTENT_URI);
             ContentProviderClient pageMetadataClient = cr.acquireContentProviderClient(PageMetadata.CONTENT_URI);
 
             // Insert required history item...
             ContentValues cv = new ContentValues();
             cv.put(BrowserContract.History.GUID, "guid1");
             cv.put(BrowserContract.History.URL, "https://mozilla.org");
@@ -160,9 +166,9 @@ public class GlobalPageMetadataTest {
 
         assertNotNull(cursor);
         try {
             assertEquals(expected, cursor.getCount());
         } finally {
             cursor.close();
         }
     }
-}
+}
\ No newline at end of file
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/icons/loader/TestLegacyLoader.java
@@ -1,28 +1,36 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.icons.loader;
 
-import android.content.ContentProvider;
 import android.graphics.Bitmap;
 
 import org.junit.Assert;
+import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
+import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
+import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserDB;
+import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.icons.IconDescriptor;
 import org.mozilla.gecko.icons.IconRequest;
 import org.mozilla.gecko.icons.IconResponse;
 import org.mozilla.gecko.icons.Icons;
 import org.robolectric.RuntimeEnvironment;
+import org.robolectric.shadows.ShadowContentResolver;
 
+import java.lang.reflect.Field;
+import java.lang.reflect.Modifier;
 import java.util.Iterator;
+import java.util.concurrent.ConcurrentHashMap;
 
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.verify;
 
 @RunWith(TestRunner.class)
@@ -33,31 +41,33 @@ public class TestLegacyLoader {
     private static final String TEST_ICON_URL_3 = "https://example.net/icon/favicon.ico";
 
     @Test
     public void testDatabaseIsQueriesForNormalRequestsWithNetworkSkipped() {
         // We're going to query BrowserProvider via LegacyLoader, and will access a database.
         // We need to ensure we close our db connection properly.
         // This is the only test in this class that actually accesses a database. If that changes,
         // move BrowserProvider registration into a @Before method, and provider.shutdown into @After.
-        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        final BrowserProvider provider = new BrowserProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
         try {
             final IconRequest request = Icons.with(RuntimeEnvironment.application)
                     .pageUrl(TEST_PAGE_URL)
                     .icon(IconDescriptor.createGenericIcon(TEST_ICON_URL))
                     .skipNetwork()
                     .build();
 
             final LegacyLoader loader = spy(new LegacyLoader());
             final IconResponse response = loader.load(request);
 
             verify(loader).loadBitmapFromDatabase(request);
             Assert.assertNull(response);
+        // Close any open db connections.
         } finally {
-            // Close any open db connections.
             provider.shutdown();
         }
     }
 
     @Test
     public void testNothingIsLoadedIfNetworkIsNotSkipped() {
         final IconRequest request = Icons.with(RuntimeEnvironment.application)
                 .pageUrl(TEST_PAGE_URL)
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/icons/preparation/TestLookupIconUrl.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/icons/preparation/TestLookupIconUrl.java
@@ -23,17 +23,16 @@ public class TestLookupIconUrl {
     private static final String TEST_ICON_URL_2 = "http://example.org/favicon.ico";
     private static final String TEST_ICON_URL_3 = "http://example.com/favicon.ico";
     private static final String TEST_ICON_URL_4 = "http://example.net/favicon.ico";
 
 
     @Before
     public void setUp() {
         MemoryStorage.get().evictAll();
-        DiskStorage.get(RuntimeEnvironment.application).evictAll();
     }
 
     @Test
     public void testNoIconUrlIsAddedByDefault() {
         final IconRequest request = Icons.with(RuntimeEnvironment.application)
                 .pageUrl(TEST_PAGE_URL)
                 .build();
 
--- a/mobile/android/app/src/test/java/org/mozilla/gecko/util/NetworkUtilsTest.java
+++ b/mobile/android/app/src/test/java/org/mozilla/gecko/util/NetworkUtilsTest.java
@@ -7,40 +7,36 @@ import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.telephony.TelephonyManager;
 
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.util.NetworkUtils.ConnectionSubType;
-import org.mozilla.gecko.util.NetworkUtils.ConnectionType;
-import org.mozilla.gecko.util.NetworkUtils.NetworkStatus;
+import org.mozilla.gecko.util.NetworkUtils.*;
 import org.robolectric.RuntimeEnvironment;
-import org.robolectric.shadow.api.Shadow;
+import org.robolectric.internal.ShadowExtractor;
 import org.robolectric.shadows.ShadowConnectivityManager;
 import org.robolectric.shadows.ShadowNetworkInfo;
 
-import static org.junit.Assert.assertEquals;
-import static org.junit.Assert.assertFalse;
-import static org.junit.Assert.assertTrue;
+import static org.junit.Assert.*;
 
 @RunWith(TestRunner.class)
 public class NetworkUtilsTest {
     private ConnectivityManager connectivityManager;
     private ShadowConnectivityManager shadowConnectivityManager;
 
     @Before
     public void setUp() {
         connectivityManager = (ConnectivityManager) RuntimeEnvironment.application.getSystemService(Context.CONNECTIVITY_SERVICE);
 
         // Not using Shadows.shadowOf(connectivityManager) because of Robolectric bug when using API23+
         // See: https://github.com/robolectric/robolectric/issues/1862
-        shadowConnectivityManager = (ShadowConnectivityManager) Shadow.extract(connectivityManager);
+        shadowConnectivityManager = (ShadowConnectivityManager) ShadowExtractor.extract(connectivityManager);
     }
 
     @Test
     public void testIsConnected() throws Exception {
         assertFalse(NetworkUtils.isConnected((ConnectivityManager) null));
 
         shadowConnectivityManager.setActiveNetworkInfo(null);
         assertFalse(NetworkUtils.isConnected(connectivityManager));
@@ -181,9 +177,9 @@ public class NetworkUtilsTest {
         );
         assertEquals(NetworkStatus.DOWN, NetworkUtils.getNetworkStatus(connectivityManager));
 
         shadowConnectivityManager.setActiveNetworkInfo(
                 ShadowNetworkInfo.newInstance(NetworkInfo.DetailedState.CONNECTED, ConnectivityManager.TYPE_MOBILE, 0, true, true)
         );
         assertEquals(NetworkStatus.UP, NetworkUtils.getNetworkStatus(connectivityManager));
     }
-}
+}
\ No newline at end of file
--- a/mobile/android/app/src/test/resources/robolectric.properties
+++ b/mobile/android/app/src/test/resources/robolectric.properties
@@ -1,3 +1,3 @@
-sdk=23
+sdk=21
 constants=org.mozilla.gecko.BuildConfig
 packageName=org.mozilla.gecko
--- a/mobile/android/base/AndroidManifest.xml.in
+++ b/mobile/android/base/AndroidManifest.xml.in
@@ -1,12 +1,11 @@
 #filter substitution
 <?xml version="1.0" encoding="utf-8"?>
 <manifest xmlns:android="http://schemas.android.com/apk/res/android"
-      xmlns:tools="http://schemas.android.com/tools"
       package="@ANDROID_PACKAGE_NAME@"
       android:installLocation="internalOnly"
       android:versionCode="@ANDROID_VERSION_CODE@"
       android:versionName="@MOZ_APP_VERSION@"
 #ifdef MOZ_ANDROID_SHARED_ID
       android:sharedUserId="@MOZ_ANDROID_SHARED_ID@"
 #endif
       >
@@ -129,17 +128,17 @@
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
 
             <intent-filter>
                 <action android:name="org.mozilla.gecko.UPDATE"/>
                 <category android:name="android.intent.category.DEFAULT" />
             </intent-filter>
 
-            <intent-filter tools:ignore="AppLinkUrlError">
+            <intent-filter>
                 <action android:name="android.intent.action.WEB_SEARCH" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <category android:name="android.intent.category.BROWSABLE" />
                 <data android:scheme="" />
                 <data android:scheme="http" />
                 <data android:scheme="https" />
             </intent-filter>
 
@@ -155,27 +154,27 @@
                  used, so we use integer resources instead of drawables, because
                  setting a drawable referenced to 0 results in errors when used
                  as a real drawable resource somewhere else. -->
             <meta-data
                 android:name="com.android.systemui.action_assist_icon"
                 android:resource="@integer/assist_launch_icon_res"/>
 
             <!-- For XPI installs from websites and the download manager. -->
-            <intent-filter tools:ignore="AppLinkUrlError">
+            <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:scheme="file" />
                 <data android:scheme="http" />
                 <data android:scheme="https" />
                 <data android:mimeType="application/x-xpinstall" />
             </intent-filter>
 
             <!-- For XPI installs from file: URLs. -->
-            <intent-filter tools:ignore="AppLinkUrlError">
+            <intent-filter>
                 <action android:name="android.intent.action.VIEW" />
                 <category android:name="android.intent.category.DEFAULT" />
                 <data android:host="" />
                 <data android:scheme="file" />
                 <data android:pathPattern=".*\\.xpi" />
             </intent-filter>
 
 #ifdef MOZ_ANDROID_BEAM
--- a/mobile/android/base/Makefile.in
+++ b/mobile/android/base/Makefile.in
@@ -22,18 +22,16 @@ GARBAGE += \
   FennecJNIWrappers.cpp \
   FennecJNIWrappers.h \
   $(NULL)
 
 GARBAGE_DIRS += classes db jars res sync services generated
 
 gradle_dir := $(topobjdir)/gradle/build/mobile/android
 
-ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-
 # The bootclasspath is functionally identical to the classpath, but allows the
 # classes given to redefine classes in core packages, such as java.lang.
 # android.jar is here as it provides Android's definition of the Java Standard
 # Library. The compatability lib here tweaks a few of the core classes to paint
 # over changes in behaviour between versions.
 JAVA_BOOTCLASSPATH := \
     $(ANDROID_SDK)/android.jar \
     $(NULL)
@@ -140,16 +138,17 @@ endif
 # java_bundled_libs.  See the note above.
 
 # uniq purloined from http://stackoverflow.com/a/16151140.
 uniq = $(if $1,$(firstword $1) $(call uniq,$(filter-out $(firstword $1),$1)))
 
 java_bundled_libs := $(call uniq,$(java_bundled_libs))
 java_bundled_libs := $(subst $(NULL) ,:,$(strip $(java_bundled_libs)))
 
+ifndef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
 GECKOVIEW_JARS = \
   constants.jar \
   gecko-R.jar \
   gecko-mozglue.jar \
   gecko-util.jar \
   gecko-view.jar \
   sync-thirdparty.jar \
   $(NULL)
@@ -171,16 +170,26 @@ FENNEC_JARS = \
   gecko-thirdparty.jar \
   services.jar \
   $(NULL)
 
 ifdef MOZ_ANDROID_MLS_STUMBLER
 FENNEC_JARS += ../stumbler/stumbler.jar
 endif
 
+else # MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+
+GECKOVIEW_JARS := $(gradle_dir)/geckoview/intermediates/bundles/debug/classes.jar
+FENNEC_JARS := $(gradle_dir)/app/intermediates/packaged/officialPhoton/debug/classes.jar
+
+$(GECKOVIEW_JARS): .gradle.deps
+$(FENNEC_JARS): .gradle.deps
+
+endif # MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+
 geckoview_jars_classpath := $(subst $(NULL) ,:,$(strip $(GECKOVIEW_JARS)))
 
 # All the jars we're compiling from source. (not to be confused with
 # java_bundled_libs, which holds the jars which we're including as binaries).
 ALL_JARS = \
   $(GECKOVIEW_JARS) \
   $(FENNEC_JARS) \
   $(NULL)
@@ -218,39 +227,37 @@ library_jars := \
 # MOZ_ANDROID_MMA requires MOZ_INSTALL_TRACKING, so we don't need a
 # separate clause for MMA (Leanplum) support.
 ifdef MOZ_INSTALL_TRACKING
 library_jars += $(ANDROID_SDK)/optional/org.apache.http.legacy.jar
 endif # MOZ_INSTALL_TRACKING
 
 library_jars := $(subst $(NULL) ,:,$(strip $(library_jars)))
 
-endif # MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
-
 ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
 define gradle_command
 $(1): $(2)
 	@$$(TOUCH) $$@
 	$$(topsrcdir)/mach android assemble-app
 endef
 
 # .gradle.deps: .aapt.deps FORCE
 $(eval $(call gradle_command,.gradle.deps,.aapt.deps FORCE))
 
 classes.dex: .gradle.deps
 	$(REPORT_BUILD)
-	cp $(gradle_dir)/app/intermediates/transforms/dexMerger/officialWithoutGeckoBinariesNoMinApiPhoton/debug/0/classes.dex classes.dex
+	cp $(gradle_dir)/app/intermediates/transforms/dex/officialPhoton/debug/folders/1000/1f/main/classes.dex $@
 
 GeneratedJNIWrappers.cpp GeneratedJNIWrappers.h GeneratedJNINatives.h : .gradle.deps
 	$(REPORT_BUILD)
 
 FennecJNIWrappers.cpp FennecJNIWrappers.h FennecJNINatives.h: .gradle.deps
 	$(REPORT_BUILD)
 
-else # MOZ_BUILD_MOBILE_ANDROID_WITH_GRADLE
+else
 classes.dex: .proguard.deps
 	$(REPORT_BUILD)
 	$(DX) --dex --output=classes.dex --force-jumbo jars-proguarded
 
 ifdef MOZ_DISABLE_PROGUARD
   PROGUARD_PASSES=0
 else
   ifdef MOZ_DEBUG
@@ -479,17 +486,17 @@ ifdef MOZ_BUILD_MOBILE_ANDROID_WITH_GRAD
 # strings.xml.
 
 # .gradle.nodeps: AndroidManifest.xml generated/preprocessed/org/mozilla/gecko/AppConstants.java ... FORCE
 $(eval $(call gradle_command,.gradle.nodeps,AndroidManifest.xml $(constants_PP_JAVAFILES) FORCE))
 
 .aapt.nodeps: .gradle.nodeps FORCE
 	@$(TOUCH) $@
 	cp $(GRADLE_ANDROID_APP_APK) gecko-nodeps.ap_
-	cp $(gradle_dir)/app/intermediates/transforms/dexMerger/officialWithoutGeckoBinariesNoMinApiPhoton/debug/0/classes.dex classes.dex
+	cp $(gradle_dir)/app/intermediates/transforms/dex/officialPhoton/debug/folders/1000/1f/main/classes.dex classes.dex
 else
 # .aapt.nodeps: AndroidManifest.xml FORCE
 $(eval $(call aapt_command,.aapt.nodeps,AndroidManifest.xml FORCE,gecko-nodeps.ap_,gecko-nodeps/,gecko-nodeps/))
 endif
 
 # Override the Java settings with some specific android settings
 include $(topsrcdir)/config/android-common.mk
 
--- a/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/CrashReporter.java
@@ -41,19 +41,17 @@ import android.os.Handler;
 import android.support.v7.app.AppCompatActivity;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.View;
 import android.widget.CheckBox;
 import android.widget.CompoundButton;
 import android.widget.EditText;
 
-// Registered: This activity is only registered in the manifest if MOZ_CRASHREPORTER is set.
-// CutPasteId: This lint is not worth fixing.  To fix it, cache all the findViewById results.
-@SuppressLint("Registered,CutPasteId")
+@SuppressLint("Registered") // This activity is only registered in the manifest if MOZ_CRASHREPORTER is set
 public class CrashReporter extends AppCompatActivity
 {
     private static final String LOGTAG = "GeckoCrashReporter";
 
     private static final String PASSED_MINI_DUMP_KEY = "minidumpPath";
     private static final String PASSED_MINI_DUMP_SUCCESS_KEY = "minidumpSuccess";
     private static final String MINI_DUMP_PATH_KEY = "upload_file_minidump";
     private static final String PAGE_URL_KEY = "URL";
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/ActivityStreamTelemetry.java
@@ -1,16 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.activitystream;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.content.res.Resources;
 import android.support.annotation.NonNull;
 
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
@@ -212,17 +211,18 @@ public class ActivityStreamTelemetry {
                     case BrowserContract.TopSites.TYPE_SUGGESTED:
                         this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_SUGGESTED);
                         break;
                     case BrowserContract.TopSites.TYPE_TOP:
                         this.set(Contract.SOURCE_SUBTYPE, Contract.SUBTYPE_TOP);
                         break;
                     // While we also have a "blank" type, it is not used by Activity Stream.
                     case BrowserContract.TopSites.TYPE_BLANK:
-                        throw new IllegalStateException("Unknown top site type :" + (int) topSite.getType());
+                    default:
+                        throw new IllegalStateException("Unknown top site type: " + topSite.getType());
                 }
 
                 return this;
             }
 
             public String build() {
                 return data.toString();
             }
--- a/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/Highlight.java
+++ b/mobile/android/base/java/org/mozilla/gecko/activitystream/homepanel/model/Highlight.java
@@ -221,10 +221,9 @@ public class Highlight implements Webpag
 
     @Override
     public long getUniqueId() {
         return historyId;
     }
 
     // The Highlights cursor automatically notifies of data changes, so nothing needs to be done here.
     @Override
-    public void onStateCommitted() {}
-}
+    public void onStateCommitted() {}}
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/ActionBarPresenter.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/ActionBarPresenter.java
@@ -29,16 +29,17 @@ import android.widget.ImageView;
 import android.widget.TextView;
 
 import org.mozilla.gecko.GeckoView;
 import org.mozilla.gecko.GeckoSession.ProgressListener.SecurityInformation;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.SiteIdentity;
 import org.mozilla.gecko.Tab;
 import org.mozilla.gecko.toolbar.SecurityModeUtil;
+import org.mozilla.gecko.toolbar.CustomTabsSecurityPopup;
 import org.mozilla.gecko.util.ColorUtil;
 
 /**
  * This class is used to maintain appearance of ActionBar of CustomTabsActivity, includes background
  * color, custom-view and so on.
  */
 public class ActionBarPresenter {
 
--- a/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsSecurityPopup.java
+++ b/mobile/android/base/java/org/mozilla/gecko/customtabs/CustomTabsSecurityPopup.java
@@ -1,13 +1,13 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-package org.mozilla.gecko.customtabs;
+package org.mozilla.gecko.toolbar;
 
 import android.content.ClipData;
 import android.content.ClipboardManager;
 import android.content.res.Resources;
 import android.graphics.Bitmap;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
 import android.support.design.widget.Snackbar;
--- a/mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/AbstractTransactionalProvider.java
@@ -37,16 +37,18 @@ import java.util.Arrays;
  * primarily to avoid the pattern of a transaction being begun, a read occurring,
  * and then a write being necessary. This lock upgrade can result in SQLITE_BUSY,
  * which we don't handle well. Better to avoid starting a transaction too soon!
  *
  * You are probably interested in some subclasses:
  *
  * * {@link AbstractPerProfileDatabaseProvider} provides a simple abstraction for
  *   querying databases that are stored in the user's profile directory.
+ * * {@link PerProfileDatabaseProvider} is a simple version that only allows a
+ *   single ContentProvider to access each per-profile database.
  * * {@link SharedBrowserDatabaseProvider} is an example of a per-profile provider
  *   that allows for multiple providers to safely work with the same databases.
  */
 @SuppressWarnings("javadoc")
 public abstract class AbstractTransactionalProvider extends ContentProvider {
     private static final String LOGTAG = "GeckoTransProvider";
 
     private static final boolean logDebug = Log.isLoggable(LOGTAG, Log.DEBUG);
@@ -127,17 +129,17 @@ public abstract class AbstractTransactio
             db.setTransactionSuccessful();
         }
     }
 
     /**
      * If we're not in a batch, but we are in a write transaction,
      * end it.
      *
-     * @see AbstractTransactionalProvider#markWriteSuccessful(SQLiteDatabase)
+     * @see PerProfileDatabaseProvider#markWriteSuccessful(SQLiteDatabase)
      */
     protected void endWrite(final SQLiteDatabase db) {
         if (isInBatch()) {
             trace("Not ending write: inside batch operation.");
             return;
         }
 
         if (db.inTransaction()) {
--- a/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
+++ b/mobile/android/base/java/org/mozilla/gecko/db/LocalBrowserDB.java
@@ -301,21 +301,17 @@ public class LocalBrowserDB extends Brow
                 // For now, each name is expected to be unique, and duplicate
                 // icons will be duplicated in the DB. See Bug 1040806 Comment 8.
                 if (iconValue != null) {
                     final int faviconID = faviconIDs.get(name);
                     iconValue.put("_id", faviconID);
                     bookmarkValue.put(Bookmarks.FAVICON_ID, faviconID);
                     faviconValues.add(iconValue);
                 }
-            } catch (IllegalAccessException e) {
-                Log.wtf(LOGTAG, "Reflection failure.", e);
-            } catch (IllegalArgumentException e) {
-                Log.wtf(LOGTAG, "Reflection failure.", e);
-            } catch (NoSuchFieldException e) {
+            } catch (IllegalAccessException | IllegalArgumentException | NoSuchFieldException e) {
                 Log.wtf(LOGTAG, "Reflection failure.", e);
             }
         }
 
         if (!faviconValues.isEmpty()) {
             try {
                 cr.bulkInsert(mFaviconsUriWithProfile, faviconValues.toArray(new ContentValues[faviconValues.size()]));
             } catch (Exception e) {
@@ -501,23 +497,17 @@ public class LocalBrowserDB extends Brow
         try {
             Class<?> drawablesClass = R.raw.class;
 
             // Look for a favicon with the id R.raw.bookmarkdefaults_favicon_*.
             Field faviconField = drawablesClass.getField(name.replace("_title_", "_favicon_"));
             faviconField.setAccessible(true);
 
             return faviconField.getInt(null);
-        } catch (IllegalAccessException e) {
-            // We'll end up here for any default bookmark that doesn't have a favicon in
-            // resources/raw/ (i.e., about:firefox). When this happens, the Favicons service will
-            // fall back to the default branding icon for about pages. Non-about pages should always
-            // specify an icon; otherwise, the placeholder globe favicon will be used.
-            Log.d(LOGTAG, "No raw favicon resource found for " + name);
-        } catch (NoSuchFieldException e) {
+        } catch (IllegalAccessException | NoSuchFieldException e) {
             // We'll end up here for any default bookmark that doesn't have a favicon in
             // resources/raw/ (i.e., about:firefox). When this happens, the Favicons service will
             // fall back to the default branding icon for about pages. Non-about pages should always
             // specify an icon; otherwise, the placeholder globe favicon will be used.
             Log.d(LOGTAG, "No raw favicon resource found for " + name);
         }
 
         Log.e(LOGTAG, "Failed to find favicon resource ID for " + name);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/db/PerProfileDatabaseProvider.java
@@ -0,0 +1,55 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.db;
+
+import org.mozilla.gecko.AppConstants.Versions;
+import org.mozilla.gecko.db.PerProfileDatabases.DatabaseHelperFactory;
+
+import android.content.Context;
+import android.database.sqlite.SQLiteOpenHelper;
+
+/**
+ * Abstract class containing methods needed to make a SQLite-based content
+ * provider with a database helper of type T, where one database helper is
+ * held per profile.
+ */
+public abstract class PerProfileDatabaseProvider<T extends SQLiteOpenHelper> extends AbstractPerProfileDatabaseProvider {
+    private PerProfileDatabases<T> databases;
+
+    @Override
+    protected PerProfileDatabases<T> getDatabases() {
+        return databases;
+    }
+
+    protected abstract String getDatabaseName();
+
+    /**
+     * Creates and returns an instance of the appropriate DB helper.
+     *
+     * @param  context       to use to create the database helper
+     * @param  databasePath  path to the DB file
+     * @return               instance of the database helper
+     */
+    protected abstract T createDatabaseHelper(Context context, String databasePath);
+
+    @Override
+    public boolean onCreate() {
+        synchronized (this) {
+            databases = new PerProfileDatabases<T>(
+                getContext(), getDatabaseName(), new DatabaseHelperFactory<T>() {
+                    @Override
+                    public T makeDatabaseHelper(Context context, String databasePath) {
+                        final T helper = createDatabaseHelper(context, databasePath);
+                        if (Versions.feature16Plus) {
+                            helper.setWriteAheadLoggingEnabled(true);
+                        }
+                        return helper;
+                    }
+                });
+        }
+
+        return true;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/BaseAction.java
@@ -1,16 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.dlc;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.support.annotation.IntDef;
 import android.util.Log;
 
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.background.nativecode.NativeCrypto;
 import org.mozilla.gecko.dlc.catalog.DownloadContent;
 import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
@@ -96,17 +95,17 @@ public abstract class BaseAction {
     protected File getDestinationFile(Context context, DownloadContent content)
             throws UnrecoverableDownloadContentException, RecoverableDownloadContentException {
         File destinationDirectory;
         if (content.isFont()) {
             destinationDirectory = new File(context.getApplicationInfo().dataDir, "fonts");
         } else if (content.isHyphenationDictionary()) {
             destinationDirectory = new File(context.getApplicationInfo().dataDir, "hyphenation");
         } else {
-            throw new UnrecoverableDownloadContentException("Can't determine destination for kind: " + (String) content.getKind());
+            throw new UnrecoverableDownloadContentException("Can't determine destination for kind: " + content.getKind());
         }
 
         if (!destinationDirectory.exists() && !destinationDirectory.mkdirs()) {
             throw new RecoverableDownloadContentException(RecoverableDownloadContentException.DISK_IO,
                     "Destination directory does not exist and cannot be created");
         }
 
         return new File(destinationDirectory, content.getFilename());
--- a/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
+++ b/mobile/android/base/java/org/mozilla/gecko/dlc/DownloadAction.java
@@ -1,16 +1,15 @@
 /* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.dlc;
 
-import android.annotation.SuppressLint;
 import android.content.Context;
 import android.net.ConnectivityManager;
 import android.net.NetworkInfo;
 import android.support.v4.net.ConnectivityManagerCompat;
 import android.util.Log;
 
 import org.mozilla.gecko.dlc.catalog.DownloadContent;
 import org.mozilla.gecko.dlc.catalog.DownloadContentCatalog;
@@ -102,17 +101,17 @@ public class DownloadAction extends Base
                 if (!verify(temporaryFile, content.getDownloadChecksum())) {
                     Log.w(LOGTAG, "Wrong checksum after download, content=" + content.getId());
                     temporaryFile.delete();
                     DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_CHECKSUM);
                     continue;
                 }
 
                 if (!content.isAssetArchive()) {
-                    Log.e(LOGTAG, "Downloaded content is not of type 'asset-archive': " + (String) content.getType());
+                    Log.e(LOGTAG, "Downloaded content is not of type 'asset-archive': " + content.getType());
                     temporaryFile.delete();
                     DownloadContentTelemetry.eventDownloadFailure(content, DownloadContentTelemetry.ERROR_LOGIC);
                     continue;
                 }
 
                 extract(temporaryFile, destinationFile, content.getChecksum());
 
                 catalog.markAsDownloaded(content);
new file mode 100644
--- /dev/null
+++ b/mobile/android/base/java/org/mozilla/gecko/home/HomeExpandableListView.java
@@ -0,0 +1,68 @@
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+package org.mozilla.gecko.home;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.ContextMenu.ContextMenuInfo;
+import android.view.View;
+import android.widget.AdapterView;
+import android.widget.AdapterView.OnItemLongClickListener;
+import android.widget.ExpandableListView;
+
+/**
+ * <code>HomeExpandableListView</code> is a custom extension of
+ * <code>ExpandableListView<code>, that packs a <code>HomeContextMenuInfo</code>
+ * when any of its rows is long pressed.
+ * <p>
+ * This is the <code>ExpandableListView</code> equivalent of
+ * <code>HomeListView</code>.
+ */
+public class HomeExpandableListView extends ExpandableListView
+                                    implements OnItemLongClickListener {
+
+    // ContextMenuInfo associated with the currently long pressed list item.
+    private HomeContextMenuInfo mContextMenuInfo;
+
+    // ContextMenuInfo factory.
+    private HomeContextMenuInfo.ExpandableFactory mContextMenuInfoFactory;
+
+    public HomeExpandableListView(Context context) {
+        this(context, null);
+    }
+
+    public HomeExpandableListView(Context context, AttributeSet attrs) {
+        this(context, attrs, 0);
+    }
+
+    public HomeExpandableListView(Context context, AttributeSet attrs, int defStyle) {
+        super(context, attrs, defStyle);
+
+        setOnItemLongClickListener(this);
+    }
+
+    @Override
+    public boolean onItemLongClick(AdapterView<?> parent, View view, int position, long id) {
+        if (mContextMenuInfoFactory == null) {
+            return false;
+        }
+
+        // HomeExpandableListView items can correspond to groups and children.
+        // The factory can determine whether to add context menu for either,
+        // both, or none by unpacking the given position.
+        mContextMenuInfo = mContextMenuInfoFactory.makeInfoForAdapter(view, position, id, getExpandableListAdapter());
+        return showContextMenuForChild(HomeExpandableListView.this);
+    }
+
+    @Override
+    public ContextMenuInfo getContextMenuInfo() {
+        return mContextMenuInfo;
+    }
+
+    public void setContextMenuInfoFactory(final HomeContextMenuInfo.ExpandableFactory factory) {
+        mContextMenuInfoFactory = factory;
+    }
+}
--- a/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
+++ b/mobile/android/base/java/org/mozilla/gecko/updater/UpdateService.java
@@ -289,24 +289,22 @@ public class UpdateService extends Inten
         Permissions.from(this)
                 .withPermissions(Manifest.permission.WRITE_EXTERNAL_STORAGE)
                 .doNotPrompt()
                 .andFallback(new Runnable() {
                     @Override
                     public void run() {
                         showPermissionNotification();
                         sendCheckUpdateResult(CheckUpdateResult.NOT_AVAILABLE);
-                    }
-                })
+                    }})
                 .run(new Runnable() {
                     @Override
                     public void run() {
                         startDownload(info, flags);
-                    }
-                });
+                    }});
     }
 
     private void startDownload(UpdateInfo info, int flags) {
         AutoDownloadPolicy policy = getAutoDownloadPolicy();
 
         // We only start a download automatically if one of following criteria are met:
         //
         // - We have a FORCE_DOWNLOAD flag passed in
--- a/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
+++ b/mobile/android/base/java/org/mozilla/gecko/util/UnusedResourcesUtil.java
@@ -96,13 +96,9 @@ final class UnusedResourcesUtil {
     // not needed while Activity Stream is part of the HomePager
     public static final int[] TEMPORARY_UNUSED_ACTIVITY_STREAM = {
             R.string.activity_stream_topsites
     };
 
     public static final int[] USED_IN_PAGE_ACTION = {
             R.drawable.add_to_homescreen
     };
-
-    public static final int[] USED_IN_LEANPLUM_EXPANDABLE_LIST_ACTIVITY = {
-            R.style.Widget_ExpandableListView,
-    };
 }
--- a/mobile/android/base/moz.build
+++ b/mobile/android/base/moz.build
@@ -560,16 +560,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'db/HomeProvider.java',
     'db/LocalBrowserDB.java',
     'db/LocalSearches.java',
     'db/LocalTabsAccessor.java',
     'db/LocalUrlAnnotations.java',
     'db/LocalURLMetadata.java',
     'db/LoginsProvider.java',
     'db/PasswordsProvider.java',
+    'db/PerProfileDatabaseProvider.java',
     'db/PerProfileDatabases.java',
     'db/RemoteClient.java',
     'db/RemoteTab.java',
     'db/Searches.java',
     'db/SearchHistoryProvider.java',
     'db/SharedBrowserDatabaseProvider.java',
     'db/SQLiteBridgeContentProvider.java',
     'db/SuggestedSites.java',
@@ -657,16 +658,17 @@ gbjar.sources += ['java/org/mozilla/geck
     'home/FramePanelLayout.java',
     'home/HistorySectionsHelper.java',
     'home/HomeAdapter.java',
     'home/HomeBanner.java',
     'home/HomeConfig.java',
     'home/HomeConfigLoader.java',
     'home/HomeConfigPrefsBackend.java',
     'home/HomeContextMenuInfo.java',
+    'home/HomeExpandableListView.java',
     'home/HomeFragment.java',
     'home/HomeListView.java',
     'home/HomePager.java',
     'home/HomePanelsManager.java',
     'home/HomeScreen.java',
     'home/ImageLoader.java',
     'home/MultiTypeCursorAdapter.java',
     'home/PanelAuthCache.java',
@@ -1155,17 +1157,17 @@ if CONFIG['MOZ_INSTALL_TRACKING']:
 gbjar.extra_jars += [CONFIG['ANDROID_APPCOMPAT_V7_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_SUPPORT_VECTOR_DRAWABLE_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_ANIMATED_VECTOR_DRAWABLE_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_CARDVIEW_V7_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_DESIGN_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_RECYCLERVIEW_V7_AAR_LIB']]
 gbjar.extra_jars += [CONFIG['ANDROID_CUSTOMTABS_AAR_LIB']]
 
-gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough,-cast', '-J-Xmx512m', '-J-Xms128m']
+gbjar.javac_flags += ['-Xlint:all,-deprecation,-fallthrough', '-J-Xmx512m', '-J-Xms128m']
 
 # gecko-thirdparty is a good place to put small independent libraries
 gtjar = add_java_jar('gecko-thirdparty')
 gtjar.sources += [ thirdparty_source_dir + f for f in [
     'com/booking/rtlviewpager/PagerAdapterWrapper.java',
     'com/booking/rtlviewpager/RtlViewPager.java',
     'com/jakewharton/disklrucache/DiskLruCache.java',
     'com/jakewharton/disklrucache/StrictLineReader.java',
rename from mobile/android/app/src/main/res/values-v17/themes.xml
rename to mobile/android/base/resources/values-v17/themes.xml
deleted file mode 100644
--- a/mobile/android/docs/gradle.rst
+++ /dev/null
@@ -1,101 +0,0 @@
-.. -*- Mode: rst; fill-column: 80; -*-
-
-=================================
- The Fennec Gradle configuration
-=================================
-
-The Fennec Gradle configuration has three major facets:
-
-1. separation into Gradle projects;
-2. Android-Gradle build configurations using `flavorDimensions`;
-3. integration into the larger `moz.build` build system.
-
-Separation into Gradle projects
-===============================
-
-The Fennec source code is separated into multiple Gradle projects.  Right now,
-there are only a few: `:app`, `:geckoview`, `:thirdparty`, etc.  Over time, pieces
-of the Fennec source code will be extracted into separate, more-or-less
-stand-alone Gradle projects (e.g., `:services`, `:stumbler`, `:media`) and use
-modern techniques such as dependency injection to configure themselves at
-runtime.
-
-The `:omnijar` project is special and exists only to support integration with
-Android Studio.
-
-The details of the Gradle projects are reflected in the root `settings.gradle`
-and the `**/build.gradle` files throughout the tree.
-
-Android-Gradle build configurations
-===================================
-
-The Fennec `:app` project uses the Android-Gradle build plugin
-`flavorDimensions` feature set to support many different configurations.  The
-Gradle integration must support many often conflicting requirements; the flavor
-dimensions chosen support these requirements.
-
-Version 3.0+ of the Android-Gradle build plugin improves support for "variant
-dependencies".  This makes it easier for a consuming application (for us,
-`:app`) to use the appropriate configuration of consumed libraries (for us,
-`:geckoview`).  This allows us to simplify the logic around packaging the Gecko
-libraries and Omnijar.
-
-The details of the Android-Gradle build configurations are reflected
-`**/build.gradle` files throughout the tree, in the
-`mobile/android/gradle/*.gradle` files, and in the configuration baked into
-`mobile/android/gradle.configure`.
-
-The notable flavor dimensions are:
-
-audience
---------
-
-The `audience` flavor dimension determines who the build is for.  "local"
-audiences are developers: they should get extra logging and as much support for
-building Fennec as possible.  In particular, "local" audiences get support for
-modifying the Android manifest and regenerating the pre-processed `strings.xml`
-files while building with Gradle from within Android Studio.
-
-"official" audiences are end users: they should get Mozilla's official branding
-and have security-sensitive developer features disabled.  The "official"
-audience corresponds roughly to the `MOZILLA_OFFICIAL=1` build setting.
-
-**Builds shipped to the Google Play Store are always for "official" audiences.**
-
-geckoBinaries
--------------
-
-For deep historical reasons, Mozilla's build system has multiple stages, the
-most important of which are the build stage and the package stage.  During the
-build stage, the Gecko compiled libraries (e.g., `libxul.so`) and the Omnijar
-have not yet been built.  These libraries are only available during the package
-stage.  Gradle builds always want to include the Gecko libraries and the
-Omnijar.
-
-To accommodate the different stages, the build stage always invokes
-"withoutGeckoBinaries" Gradle configurations. These configurations don't expect
-or use the Gecko libraries or Omnijar.  At the moment, the package stage also
-invokes "withoutGeckoBinaries", but in the future, the package stage will invoke
-"withGeckoBinaries" Gradle configurations to simplify the packaging of libraries
-and the omnijar.
-
-**Local developers almost always want to build "withGeckoBinaries", so that the
-APK files produced can be run on device.**
-
-minApi
-------
-
-At various times in the past, Fennec has supported APK splits, producing APKs
-that support only specific Android versions.  While this is not used at this
-time, there are certain developer options (i.e., options that should only apply
-to "local" audiences) that *also* depend on the target Android version.  This
-flavor dimension allows to opt in to those options, improving the speed of
-development.
-
-Integration into the larger `moz.build` build system
-====================================================
-
-The details of the Gradle integration into the larger `moz.build` system are
-mostly captured in configuration baked into `mobile/android/gradle.configure`.
-This configuration is reflected in the Android-specific `mach android *`
-commands defined in `mobile/android/mach_commands.py`.
--- a/mobile/android/docs/index.rst
+++ b/mobile/android/docs/index.rst
@@ -15,16 +15,15 @@ Contents:
    uitelemetry
    activitystreamtelemetry
    downloadcontenttelemetry
    adjust
    mma
    defaultdomains
    shutdown
    push
-   gradle
 
 Indices and tables
 ==================
 
 * :ref:`genindex`
 * :ref:`modindex`
 * :ref:`search`
--- a/mobile/android/geckoview/build.gradle
+++ b/mobile/android/geckoview/build.gradle
@@ -1,14 +1,12 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/geckoview"
 
 apply plugin: 'com.android.library'
 
-apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"
-
 // This converts MOZ_APP_VERSION into an integer
 // version code.
 //
 // We take something like 58.1.2a1 and come out with 5800102
 // This gives us 3 digits for the major number, and 2 digits
 // each for the minor and build number. Beta and Release
 def computeVersionCode() {
     String appVersion = mozconfig.substs.MOZ_APP_VERSION
@@ -35,18 +33,22 @@ def computeVersionCode() {
         code += Integer.parseInt(parts[2])
     }
 
     return code;
 }
 
 android {
     compileSdkVersion project.ext.compileSdkVersion
+    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
+        defaultPublishConfig 'release'
+        publishNonDefault true
+
         targetSdkVersion project.ext.targetSdkVersion
         minSdkVersion project.ext.minSdkVersion
         manifestPlaceholders = project.ext.manifestPlaceholders
 
         versionCode computeVersionCode()
         versionName "${mozconfig.substs.MOZ_APP_VERSION}-${mozconfig.substs.MOZ_UPDATE_CHANNEL}"
         consumerProguardFiles 'proguard-rules.txt'
 
@@ -96,18 +98,24 @@ android {
 
         // Official corresponds, roughly, to whether this build is performed on
         // Mozilla's continuous integration infrastructure. You should disable
         // developer-only functionality when this flag is set.
         // This makes no sense for GeckoView and should be removed as soon as possible.
         buildConfigField 'boolean', 'MOZILLA_OFFICIAL', mozconfig.substs.MOZILLA_OFFICIAL ? 'true' : 'false';
     }
 
-    project.configureProductFlavors.delegate = it
-    project.configureProductFlavors()
+    buildTypes {
+        withGeckoBinaries {
+            initWith release
+        }
+        withoutGeckoBinaries { // For clarity and consistency throughout the tree.
+            initWith release
+        }
+    }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
     }
 
     dexOptions {
         javaMaxHeapSize "2g"
@@ -141,26 +149,31 @@ android {
 
             assets {
             }
         }
     }
 }
 
 dependencies {
-    implementation "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-    implementation "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:palette-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
 }
 
 apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
 
 android.libraryVariants.all { variant ->
-    // See the notes in mobile/android/app/build.gradle for details on including
-    // Gecko binaries and the Omnijar.
-    if ((variant.productFlavors*.name).contains('withGeckoBinaries')) {
+    // Like 'debug', 'release', or 'withGeckoBinaries'.
+    def buildType = variant.buildType.name
+
+    // It would be most natural for :geckoview to always include the Gecko
+    // binaries, but that's difficult; see the notes in
+    // mobile/android/gradle/with_gecko_binaries.gradle.  Instead :app uses
+    // :geckoview:release and handles it's own Gecko binary inclusion.
+    if (buildType.equals('withGeckoBinaries')) {
         configureVariantWithGeckoBinaries(variant)
     }
 
     // Javadoc and Sources JAR configuration cribbed from
     // https://github.com/mapbox/mapbox-gl-native/blob/d169ea55c1cfa85cd8bf19f94c5f023569f71810/platform/android/MapboxGLAndroidSDK/build.gradle#L85
     // informed by
     // https://code.tutsplus.com/tutorials/creating-and-publishing-an-android-library--cms-24582,
     // and amended from numerous Stackoverflow posts.
@@ -184,31 +197,31 @@ android.libraryVariants.all { variant ->
         options.source = 7
 
         options.docTitle = "GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API"
         options.header = "GeckoView ${mozconfig.substs.MOZ_APP_VERSION} API"
         options.addStringOption('noindex');
         options.addStringOption('noqualifier', 'java.lang');
     }
 
-    def javadocJar = task("javadocJar${name.capitalize()}", type: Jar, dependsOn: javadoc) {
+    task "javadocJar${name.capitalize()}"(type: Jar, dependsOn: javadoc) {
         classifier = 'javadoc'
         from javadoc.destinationDir
     }
 
-    def sourcesJar = task("sourcesJar${name.capitalize()}", type: Jar) {
+    task "sourcesJar${name.capitalize()}"(type: Jar) {
         classifier 'sources'
         description = "Generate Javadoc for build variant $name"
         destinationDir = new File(destinationDir, variant.baseName)
         from files(variant.javaCompile.source)
     }
 }
 
 android.libraryVariants.all { variant ->
-    configureLibraryVariantWithJNIWrappers(variant, "Generated")
+    configureVariantWithJNIWrappers(variant, "Generated")
 }
 
 apply plugin: 'maven'
  
 uploadArchives {
     repositories.mavenDeployer {
         pom.groupId = 'org.mozilla'
         pom.artifactId = "geckoview-${mozconfig.substs.MOZ_UPDATE_CHANNEL}-${mozconfig.substs.ANDROID_CPU_ARCH}"
@@ -227,32 +240,32 @@ uploadArchives {
 }
 
 // This is all related to the withGeckoBinaries approach; see
 // mobile/android/gradle/with_gecko_binaries.gradle.
 afterEvaluate {
     // The bundle tasks are only present when the particular configuration is
     // being built, so this task might not exist.  (This is due to the way the
     // Android Gradle plugin defines things during configuration.)
-    def bundleWithGeckoBinaries = tasks.findByName('bundleOfficialWithGeckoBinariesNoMinApiRelease')
+    def bundleWithGeckoBinaries = tasks.findByName('bundleWithGeckoBinaries')
     if (!bundleWithGeckoBinaries) {
         return
     }
 
     // Remove default configuration, which is the release configuration, when
     // we're actually building withGeckoBinaries.  This makes `gradle install`
     // install the withGeckoBinaries artifacts, not the release artifacts (which
     // are withoutGeckoBinaries and not suitable for distribution.)
     def Configuration archivesConfig = project.getConfigurations().getByName('archives')
     archivesConfig.artifacts.removeAll { it.extension.equals('aar') }
 
     artifacts {
         // Instead of default (release) configuration, publish one with Gecko binaries.
-        archives bundleOfficialWithGeckoBinariesNoMinApiRelease
+        archives bundleWithGeckoBinaries
         // Javadoc and sources for developer ergononomics.
-        archives javadocJarOfficialWithGeckoBinariesNoMinApiRelease
-        archives sourcesJarOfficialWithGeckoBinariesNoMinApiRelease
+        archives javadocJarWithGeckoBinaries
+        archives sourcesJarWithGeckoBinaries
     }
 }
 
 // Bug 1353055 - Strip 'vars' debugging information to agree with moz.build.
 apply from: "${topsrcdir}/mobile/android/gradle/debug_level.gradle"
 android.libraryVariants.all configureVariantDebugLevel
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/media/GeckoHlsPlayer.java
@@ -59,17 +59,17 @@ public class GeckoHlsPlayer implements B
      *  Gecko media pipleine (in cpp). Once HLSDemuxer is created later, we
      *  need to bridge this HLSResource to the created demuxer. And they share
      *  the same GeckoHlsPlayer.
      *  mPlayerId is a token used for Gecko media pipeline to obtain corresponding player.
      */
     private final int mPlayerId;
     private boolean mExoplayerSuspended = false;
 
-    private enum MediaDecoderPlayState {
+    private enum MediaDecoderPlayState{
         PLAY_STATE_PREPARING,
         PLAY_STATE_PAUSED,
         PLAY_STATE_PLAYING
     }
     // Default value is PLAY_STATE_PREPARING and it will be set to PLAY_STATE_PLAYING
     // once HTMLMediaElement calls PlayInternal().
     private MediaDecoderPlayState mMediaDecoderPlayState = MediaDecoderPlayState.PLAY_STATE_PREPARING;
     private DataSource.Factory mMediaDataSourceFactory;
--- a/mobile/android/geckoview_example/build.gradle
+++ b/mobile/android/geckoview_example/build.gradle
@@ -1,44 +1,68 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/geckoview_example"
 
 apply plugin: 'com.android.application'
 
-apply from: "${topsrcdir}/mobile/android/gradle/product_flavors.gradle"
-
 android {
     compileSdkVersion project.ext.compileSdkVersion
+    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
         targetSdkVersion project.ext.targetSdkVersion
         minSdkVersion project.ext.minSdkVersion
         manifestPlaceholders = project.ext.manifestPlaceholders
 
         applicationId "org.mozilla.geckoview_example"
         versionCode 1
         versionName "1.0"
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
+    // This is extremely frustrating, but the only way to do it automation for
+    // now.  Without this, we only get a "debugAndroidTest" configuration; we
+    // have no "withoutGeckoBinariesAndroidTest" configuration.
+    testBuildType "withoutGeckoBinaries"
     buildTypes {
         release {
             minifyEnabled false
             proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
         }
+        withGeckoBinaries { // For consistency with :geckoview project in Task Cluster invocations.
+            initWith debug
+        }
+        withoutGeckoBinaries { // Logical negation of withGeckoBinaries.
+            initWith debug
+        }
     }
-
-    project.configureProductFlavors.delegate = it
-    project.configureProductFlavors()
 }
 
 dependencies {
-    testImplementation 'junit:junit:4.12'
+    testCompile 'junit:junit:4.12'
 
-    implementation 'com.android.support:support-annotations:23.4.0'
+    compile 'com.android.support:support-annotations:23.4.0'
 
-    androidTestImplementation 'com.android.support.test.espresso:espresso-core:2.2.2'
-    androidTestImplementation 'com.android.support.test:runner:0.5'
+    androidTestCompile 'com.android.support.test.espresso:espresso-core:2.2.2'
+    androidTestCompile 'com.android.support.test:runner:0.5'
     // Not defining this library again results in test-app assuming 23.1.1, and the following errors:
     // "Conflict with dependency 'com.android.support:support-annotations'. Resolved versions for app (23.4.0) and test app (23.1.1) differ."
-    androidTestImplementation 'com.android.support:support-annotations:23.4.0'
+    androidTestCompile 'com.android.support:support-annotations:23.4.0'
+
+    debugCompile project(path: ':geckoview', configuration: "debug")
+    releaseCompile project(path: ':geckoview', configuration: "release")
+    withGeckoBinariesCompile project(path: ':geckoview', configuration: "withGeckoBinaries")
+    withoutGeckoBinariesCompile project(path: ':geckoview', configuration: "withoutGeckoBinaries")
+}
+
+apply from: "${topsrcdir}/mobile/android/gradle/with_gecko_binaries.gradle"
 
-    implementation project(path: ':geckoview')
+android.applicationVariants.all { variant ->
+    // Like 'debug', 'release', or 'withoutGeckoBinaries'.
+    def buildType = variant.buildType.name
+
+    // It would be most natural for :geckoview to always include the Gecko
+    // binaries, but that's difficult; see the notes in
+    // mobile/android/gradle/with_gecko_binaries.gradle.  Instead we handle our
+    // own Gecko binary inclusion.
+    if (!buildType.equals('withoutGeckoBinaries')) {
+        configureVariantWithGeckoBinaries(variant)
+    }
 }
--- a/mobile/android/gradle.configure
+++ b/mobile/android/gradle.configure
@@ -41,33 +41,30 @@ set_config('GRADLE', gradle)
 def gradle_android_build_config():
     def capitalize(s):
         # str.capitalize lower cases trailing letters.
         if s:
             return s[0].upper() + s[1:]
         else:
             return s
 
+    # It's not really possible to abstract the GeckoView details just yet; post
+    # Android-Gradle plugin 3.0+, the configurations can be more sensible and
+    # we'll do this work.
     def variant(productFlavors, buildType):
         return namespace(
             productFlavors=productFlavors,
             buildType=buildType,
             # Like 'OfficialWithoutGeckoBinariesPhotonDebug'
             name = ''.join(capitalize(t) for t in chain(productFlavors, (buildType, )))
         )
 
     return namespace(
         app=namespace(
-            variant=variant(('official', 'withoutGeckoBinaries', 'noMinApi', 'photon'), 'debug'),
-        ),
-        geckoview=namespace(
-            variant=variant(('official', 'withGeckoBinaries', 'noMinApi'), 'release'),
-        ),
-        geckoview_example=namespace(
-            variant=variant(('official', 'withGeckoBinaries', 'noMinApi'), 'debug'),
+            variant=variant(('official', 'photon'), 'debug'),
         ),
     )
 
 
 @depends(gradle_android_build_config)
 def gradle_android_app_variant_name(build_config):
     '''Like "officialPhotonDebug".'''
     def uncapitalize(s):
@@ -80,59 +77,34 @@ def gradle_android_app_variant_name(buil
 
 set_config('GRADLE_ANDROID_APP_VARIANT_NAME', gradle_android_app_variant_name)
 
 
 @depends(gradle_android_build_config)
 def gradle_android_app_tasks(build_config):
     '''Gradle tasks run by |mach android assemble-app|.'''
     return [
-        'geckoview:generateJNIWrappersForGenerated{geckoview.variant.name}'.format(geckoview=build_config.geckoview),
+        'geckoview:generateJNIWrappersForGeneratedRelease',
         'app:generateJNIWrappersForFennec{app.variant.name}'.format(app=build_config.app),
         'app:assemble{app.variant.name}'.format(app=build_config.app),
         'app:assemble{app.variant.name}AndroidTest'.format(app=build_config.app),
     ]
 
 set_config('GRADLE_ANDROID_APP_TASKS', gradle_android_app_tasks)
 
 
 @depends(gradle_android_build_config, check_build_environment)
-@imports(_from='itertools', _import='imap')
 def gradle_android_app_apks(build_config, build_env):
     '''Paths to APK files produced by |mach android assemble-app|.'''
-    def capitalize(s):
-        # str.capitalize lower cases trailing letters.
-        if s:
-            return s[0].upper() + s[1:]
-        else:
-            return s
-
-    def uncapitalize(s):
-        if s:
-            return s[0].lower() + s[1:]
-        else:
-            return s
-
-    # Like 'officialPhoton'.
-    productFlavor = uncapitalize(''.join(imap(capitalize, build_config.app.variant.productFlavors)))
-    # Like 'official-photon'.
-    product_flavor = '-'.join(build_config.app.variant.productFlavors)
-
-    substs = {
-        'topobjdir': build_env.topobjdir,
-        'productFlavor': productFlavor,
-        'product_flavor': product_flavor,
-        'buildType': build_config.app.variant.buildType,
-    }
-
-    f = '{topobjdir}/gradle/build/mobile/android/app/outputs/apk/{productFlavor}/{buildType}/app-{product_flavor}-{buildType}.apk'
-    g = '{topobjdir}/gradle/build/mobile/android/app/outputs/apk/androidTest/{productFlavor}/{buildType}/app-{product_flavor}-{buildType}-androidTest.apk'
-
-    return namespace(app_apk=f.format(**substs),
-                     app_androidTest_apk=g.format(**substs))
+    flavor = '-'.join(build_config.app.variant.productFlavors)
+    buildType = build_config.app.variant.buildType
+    f = '{}/gradle/build/mobile/android/app/outputs/apk/app-{}-{}.apk'
+    g = '{}/gradle/build/mobile/android/app/outputs/apk/app-{}-{}-androidTest.apk'
+    return namespace(app_apk=f.format(build_env.topobjdir, flavor, buildType),
+                     app_androidTest_apk=g.format(build_env.topobjdir, flavor, buildType))
 
 set_config('GRADLE_ANDROID_APP_APK', gradle_android_app_apks.app_apk)
 set_config('GRADLE_ANDROID_APP_ANDROIDTEST_APK', gradle_android_app_apks.app_androidTest_apk)
 
 
 @depends(gradle_android_build_config)
 def gradle_android_test_tasks(build_config):
     '''Gradle tasks run by |mach android test|.'''
@@ -169,23 +141,23 @@ def gradle_android_findbugs_tasks(build_
     return [
         'app:findbugsXml{app.variant.name}'.format(app=build_config.app),
         'app:findbugsHtml{app.variant.name}'.format(app=build_config.app),
     ]
 
 set_config('GRADLE_ANDROID_FINDBUGS_TASKS', gradle_android_findbugs_tasks)
 
 
-@depends(gradle_android_build_config)
-def gradle_android_archive_geckoview_tasks(build_config):
+@dependable
+def gradle_android_archive_geckoview_tasks():
     '''Gradle tasks run by |mach android archive-geckoview|.'''
     return [
-        'geckoview:assemble{geckoview.variant.name}'.format(geckoview=build_config.geckoview),
-        'geckoview_example:assemble{geckoview_example.variant.name}'.format(geckoview_example=build_config.geckoview_example),
-        'geckoview_example:assemble{geckoview_example.variant.name}AndroidTest'.format(geckoview_example=build_config.geckoview_example),
+        'geckoview:assembleWithGeckoBinaries',
+        'geckoview_example:assembleWithGeckoBinaries',
+        'geckoview_example:assembleWithGeckoBinariesAndroidTest',
         'geckoview:uploadArchives',
     ]
 
 set_config('GRADLE_ANDROID_ARCHIVE_GECKOVIEW_TASKS', gradle_android_archive_geckoview_tasks)
 
 
 @depends(
     gradle_android_app_tasks,
--- a/mobile/android/gradle/debug_level.gradle
+++ b/mobile/android/gradle/debug_level.gradle
@@ -1,17 +1,20 @@
 /* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 // Bug 1353055 - Strip 'vars' debugging information to agree with moz.build.
 ext.configureVariantDebugLevel = { variant ->
-    // Like 'debug' or 'release'.
+    // Like 'debug', 'release', or 'withGeckoBinaries'.
     def buildType = variant.buildType.name
 
+    // For :app, like 'local', 'localOld', or 'official'.  For other projects, null.
+    def audienceDimension = variant.productFlavors[0]?.name
+
     // The default is 'lines,source,vars', which includes debugging information
     // that is quite large: roughly 500kb for Fennec.  Therefore we remove
     // 'vars' unless we're producing a debug build, where it is useful.
-    if (!'debug'.equals(buildType) || (variant.productFlavors*.name).contains('official')) {
+    if (!'debug'.equals(buildType) || 'official'.equals(audienceDimension)) {
         variant.javaCompile.options.debugOptions.debugLevel = 'lines,source'
     }
 }
deleted file mode 100644
--- a/mobile/android/gradle/product_flavors.gradle
+++ /dev/null
@@ -1,48 +0,0 @@
-/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
- * This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this
- * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-ext.configureProductFlavors = {
-    flavorDimensions "audience", "geckoBinaries", "minApi"
-    productFlavors {
-        local {
-            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"
-        }
-
-        withGeckoBinaries {
-            dimension "geckoBinaries"
-        }
-
-        withoutGeckoBinaries {
-            dimension "geckoBinaries"
-        }
-
-        // For API 21+ - with pre-dexing, this will be faster for local development.
-        minApi21 {
-            dimension "minApi"
-
-            // 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.
-        noMinApi {
-            dimension "minApi"
-        }
-    }
-}
--- a/mobile/android/gradle/with_gecko_binaries.gradle
+++ b/mobile/android/gradle/with_gecko_binaries.gradle
@@ -1,27 +1,51 @@
-/* -*- Mode: Groovy; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
+/* -*- Mode: Java; c-basic-offset: 4; tab-width: 20; indent-tabs-mode: nil; -*-
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
+// We run fairly hard into a fundamental limitation of the Android Gradle
+// plugin.  There are many bugs filed about this, but
+// https://code.google.com/p/android/issues/detail?id=216978#c6 is a reason one.
+// The issue is that we need fine-grained control over when to include Gecko's
+// binary libraries into the GeckoView AAR and the Fennec APK, and that's hard
+// to achieve.  In particular:
+//
+// * :app:official* wants :geckoview to not include Gecko binaries (official
+// *  automation build, before package)
+//
+// * :geckoview:withLibraries wants :geckoview to include Gecko binaries
+// * (automation build, after package)
+//
+// * non-:app:official* wants :geckoview to include Gecko binaries (local
+// * build, always after package)
+//
+// publishNonDefault (see
+// http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Library-Publication)
+// is intended to address this, but doesn't handle our case.  That option always
+// builds *all* configurations, which fails when the required Gecko binaries
+// don't exist (automation build, before package).  So instead, we make both
+// :app and :geckoview both know how to include the Gecko binaries, and use a
+// non-default, non-published :geckoview:withGeckoBinaries configuration to
+// handle automation's needs.  Simple, right?
+
 // The omnijar inputs are listed as resource directory inputs to a dummy JAR.
 // That arrangement labels them nicely in IntelliJ.  See the comment in the
 // :omnijar project for more context.
 evaluationDependsOn(':omnijar')
 
-task buildOmnijars(type:Exec) {
+task buildOmnijar(type:Exec) {
     dependsOn rootProject.generateCodeAndResources
 
     // See comment in :omnijar project regarding interface mismatches here.
     inputs.file(project(':omnijar').sourceSets.main.resources.srcDirs).skipWhenEmpty() 
 
-    // Produce both the Fennec and the GeckoView omnijars.
+    // Produce a single output file.
     outputs.file "${topobjdir}/dist/fennec/assets/omni.ja"
-    outputs.file "${topobjdir}/dist/geckoview/assets/omni.ja"
 
     workingDir "${topobjdir}"
 
     commandLine mozconfig.substs.GMAKE
     args '-C'
     args "${topobjdir}/mobile/android/base"
     args 'gradle-omnijar'
 
@@ -31,154 +55,134 @@ task buildOmnijars(type:Exec) {
     errorOutput = standardOutput
     doLast {
         if (execResult.exitValue != 0) {
             throw new GradleException("Process '${commandLine}' finished with non-zero exit value ${execResult.exitValue}:\n\n${standardOutput.toString()}")
         }
     }
 }
 
-ext.configureVariantWithGeckoBinaries = { variant ->
-    // :app needs the full Fennec omni.ja, whereas other projects need the
-    // GeckoView-specific omni.ja.
+task syncOmnijarFromDistDir(type: Sync) {
+    // :app needs the full Fennec omni.ja, whereas other projects need the GeckoView-specific omni.ja.
     def omnijar_dir = "app".equals(project.name) ? "fennec" : "geckoview"
-    def distDir = "${topobjdir}/dist/${omnijar_dir}"
+    into("${project.buildDir}/generated/omnijar")
+    from("${topobjdir}/dist/${omnijar_dir}/omni.ja",
+         "${topobjdir}/dist/${omnijar_dir}/assets/omni.ja") {
+        // Throw an exception if we find multiple, potentially conflicting omni.ja files.
+        duplicatesStrategy 'fail'
+    }
+}
 
-    def syncOmnijarFromDistDir = task("syncOmnijarFromDistDirFor${variant.name.capitalize()}", type: Sync) {
-        doFirst {
-            if (source.empty) {
-                throw new GradleException("Required omnijar not found in ${source.asPath}.  Have you built and packaged?")
-            }
-        }
-
-        into("${project.buildDir}/moz.build/src/${variant.name}/omnijar")
-        from("${distDir}/omni.ja",
-             "${distDir}/assets/omni.ja") {
-            // Throw an exception if we find multiple, potentially conflicting omni.ja files.
-            duplicatesStrategy 'fail'
+task checkLibsExistInDistDir {
+    doLast {
+        if (syncLibsFromDistDir.source.empty) {
+            throw new GradleException("Required JNI libraries not found in ${topobjdir}/dist/fennec/lib.  Have you built and packaged?")
         }
     }
-
-    def syncLibsFromDistDir = task("syncLibsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
-        doFirst {
-            if (source.empty) {
-                throw new GradleException("Required JNI libraries not found in ${source.asPath}.  Have you built and packaged?")
-            }
-        }
+}
 
-        into("${project.buildDir}/moz.build/src/${variant.name}/jniLibs")
-        from("${distDir}/lib")
-    }
+task syncLibsFromDistDir(type: Sync, dependsOn: checkLibsExistInDistDir) {
+    into("${project.buildDir}/generated/jniLibs")
+    from("${topobjdir}/dist/fennec/lib")
+}
 
-    def syncAssetsFromDistDir = task("syncAssetsFromDistDirFor${variant.name.capitalize()}", type: Sync) {
-        doFirst {
-            if (source.empty) {
-                throw new GradleException("Required assets not found in ${source.asPath}.  Have you built and packaged?")
-            }
-        }
-
-        into("${project.buildDir}/moz.build/src/${variant.name}/assets")
-        from("${distDir}/assets") {
-            exclude 'omni.ja'
+task checkAssetsExistInDistDir {
+    doLast {
+        if (syncAssetsFromDistDir.source.empty) {
+            throw new GradleException("Required assets not found in ${topobjdir}/dist/fennec/assets.  Have you built and packaged?")
         }
     }
-
-    // Local (read, not 'official') builds want to reflect developer changes to
-    // the Omnijar sources.  To do this, the Gradle build calls out to the
-    // moz.build system, which can be re-entrant.  Official builds are driven by
-    // the moz.build system and should never be re-entrant in this way.
-    if (!((variant.productFlavors*.name).contains('official'))) {
-        syncOmnijarFromDistDir.dependsOn buildOmnijars
-    }
+}
 
-    def assetGenTask = tasks.findByName("generate${variant.name.capitalize()}Assets")
-    if ((variant.productFlavors*.name).contains('withGeckoBinaries')) {
-        assetGenTask.dependsOn syncOmnijarFromDistDir
-        assetGenTask.dependsOn syncLibsFromDistDir
-        assetGenTask.dependsOn syncAssetsFromDistDir
-
-        android.sourceSets."${variant.name}".assets.srcDir syncOmnijarFromDistDir.destinationDir
-        android.sourceSets."${variant.name}".assets.srcDir syncAssetsFromDistDir.destinationDir
-        android.sourceSets."${variant.name}".jniLibs.srcDir syncLibsFromDistDir.destinationDir
+task syncAssetsFromDistDir(type: Sync, dependsOn: checkAssetsExistInDistDir) {
+    into("${project.buildDir}/generated/assets")
+    from("${topobjdir}/dist/fennec/assets") {
+        exclude 'omni.ja'
     }
 }
 
-ext.configureLibraryVariantWithJNIWrappers = { variant, module ->
-    // Library variants have two essentially independent transform* tasks:
-    //
-    // - ...WithSyncLibJars... is used by assemble* and bundle*
-    // - ...WithPrepareIntermediateJars... is used by consuming applications.
+ext.configureVariantWithGeckoBinaries = { variant ->
+    // Like 'localPhoton' or 'localOldPhoton'; may be null.
+    def productFlavor = ""
+    def productFlavorNames = variant.productFlavors.collect { it.name.capitalize() }
+    if (!productFlavorNames.isEmpty()) {
+        productFlavor = productFlavorNames.join()
+        // Groovy's `uncapitilize` is not yet available.
+        def c = productFlavor.toCharArray()
+        c[0] = Character.toLowerCase(c[0])
+        productFlavor = new String(c)
+    }
+
+    // Like 'debug' or 'release'.
+    def buildType = variant.buildType.name
+
+    syncOmnijarFromDistDir.dependsOn buildOmnijar
+    def generateAssetsTask = tasks.findByName("generate${productFlavor.capitalize()}${buildType.capitalize()}Assets")
+    generateAssetsTask.dependsOn syncOmnijarFromDistDir
+    generateAssetsTask.dependsOn syncLibsFromDistDir
+    generateAssetsTask.dependsOn syncAssetsFromDistDir
+
+    def sourceSet = productFlavor ? "${productFlavor}${buildType.capitalize()}" : buildType
+    android.sourceSets."${sourceSet}".assets.srcDir syncOmnijarFromDistDir.destinationDir
+    android.sourceSets."${sourceSet}".assets.srcDir syncAssetsFromDistDir.destinationDir
+    android.sourceSets."${sourceSet}".jniLibs.srcDir syncLibsFromDistDir.destinationDir
+}
+
+ext.configureVariantWithJNIWrappers = { variant, module ->
+    def jarTask
+    if (module == 'Generated') {
+        jarTask = tasks["package${variant.name.capitalize()}JarArtifact"]
+    } else {
+        jarTask = tasks["jar${variant.name.capitalize()}Classes"]
+    }
+
+    if (jarTask.outputs.files.size() != 1) {
+        throw new GradleException("Jar task output multiple files other than one single jar")
+    }
+
+    // At configuration time, the classpath of dependencies with internal_impl
+    // JAR files may not be correct.  This manifests in
     //
-    // It's not really possible to insert something immediately _after_
-    // ...WithPrepareIntermediateJars..., so we make the consuming moz.build
-    // system invoke this target directly, and force the
-    // ...WithPrepareIntermediateJars... dependency.  The real consumer of the
-    // JNI wrappers is the moz.build system, which always builds geckoview to
-    // consume from Fennec, so that dependency likely adds less to the build time.
-    def jarTask = tasks["transformClassesAndResourcesWithPrepareIntermediateJarsFor${variant.name.capitalize()}"]
-    def output = jarTask.outputs.files.find({ it.absolutePath.contains('/classes.jar') })
+    // 'Exception in thread "main" java.lang.NoClassDefFoundError: android/support/v4/app/ActivityCompatApi23$RequestPermissionsRequestCodeValidator'
+    //
+    // when running |mach gradle clean app:generateJNI...|.  We work around this
+    // by configuring the classpath at evaluation-time, not configuration-time.
+    //
+    // The specific dependency on the `prepareDependencies` task may not be
+    // necessary, but commits like
+    // https://github.com/evant/gradle-retrolambda/commit/15108c65ee43be499a1359d9d4f88b0851d46769
+    // suggest that it is.  It certainly doesn't hurt.
+    def prepareDependenciesTask = tasks.getByName("prepare${variant.name.capitalize()}Dependencies")
 
     def wrapperTask
     if (System.env.IS_LANGUAGE_REPACK == '1') {
         // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and don't
         // really have a build environment.
         wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}")
     } else {
         wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}", type: JavaExec) {
             classpath "${topobjdir}/build/annotationProcessors/annotationProcessors.jar"
-        
+    
             // Configure the classpath at evaluation-time, not at
             // configuration-time: see above comment.
             doFirst {
                 classpath variant.javaCompile.classpath
                 // Include android.jar.
                 classpath variant.javaCompile.options.bootClasspath
             }
     
             main = 'org.mozilla.gecko.annotationProcessors.AnnotationProcessor'
             args module
-            args output
-            
+            args jarTask.outputs.files.iterator().next()
+    
             workingDir "${topobjdir}/mobile/android/base"
-            
+    
             dependsOn jarTask
+            dependsOn prepareDependenciesTask
         }
     }
 
     if (module == 'Generated') {
         tasks["bundle${variant.name.capitalize()}"].dependsOn wrapperTask
     } else {
         tasks["assemble${variant.name.capitalize()}"].dependsOn wrapperTask
     }
 }
-
-ext.configureApplicationVariantWithJNIWrappers = { variant, module ->
-    def jarTask = tasks["bundleAppClasses${variant.name.capitalize()}"]
-    def output = jarTask.outputs.files.find({ it.absolutePath.contains('/classes.jar') })
-
-    def wrapperTask
-    if (System.env.IS_LANGUAGE_REPACK == '1') {
-        // Single-locale l10n repacks set `IS_LANGUAGE_REPACK=1` and don't
-        // really have a build environment.
-        wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}")
-    } else {
-        wrapperTask = task("generateJNIWrappersFor${module}${variant.name.capitalize()}", type: JavaExec) {
-            classpath "${topobjdir}/build/annotationProcessors/annotationProcessors.jar"
-        
-            // Configure the classpath at evaluation-time, not at
-            // configuration-time: see above comment.
-            doFirst {
-                classpath variant.javaCompile.classpath
-                // Include android.jar.
-                classpath variant.javaCompile.options.bootClasspath
-            }
-        
-            main = 'org.mozilla.gecko.annotationProcessors.AnnotationProcessor'
-            args module
-            args output
-        
-            workingDir "${topobjdir}/mobile/android/base"
-    
-            // This forces bundling, which isn't usually part of the assemble* process.
-            dependsOn jarTask
-        }
-    }
-}
--- a/mobile/android/mach_commands.py
+++ b/mobile/android/mach_commands.py
@@ -409,19 +409,17 @@ class MachCommands(MachCommandBase):
             # Avoid logging the command
             self.log_manager.terminal_handler.setLevel(logging.CRITICAL)
 
         # In automation, JAVA_HOME is set via mozconfig, which needs
         # to be specially handled in each mach command. This turns
         # $JAVA_HOME/bin/java into $JAVA_HOME.
         java_home = os.path.dirname(os.path.dirname(self.substs['JAVA']))
 
-        gradle_flags = self.substs.get('GRADLE_FLAGS', '') or \
-                       os.environ.get('GRADLE_FLAGS', '')
-        gradle_flags = shell_split(gradle_flags)
+        gradle_flags = shell_split(self.substs.get('GRADLE_FLAGS', ''))
 
         # We force the Gradle JVM to run with the UTF-8 encoding, since we
         # filter strings.xml, which is really UTF-8; the ellipsis character is
         # replaced with ??? in some encodings (including ASCII).  It's not yet
         # possible to filter with encodings in Gradle
         # (https://github.com/gradle/gradle/pull/520) and it's challenging to
         # do our filtering with Gradle's Ant support.  Moreover, all of the
         # Android tools expect UTF-8: see
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/DelegatingTestContentProvider.java
@@ -3,84 +3,41 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.background.db;
 
 import android.content.ContentProvider;
 import android.content.ContentProviderOperation;
 import android.content.ContentProviderResult;
 import android.content.ContentValues;
-import android.content.Context;
 import android.content.OperationApplicationException;
-import android.content.pm.ProviderInfo;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.support.annotation.Nullable;
 
 import org.mozilla.gecko.db.BrowserContract;
-import org.mozilla.gecko.db.BrowserProvider;
-import org.mozilla.gecko.db.TabsProvider;
-import org.robolectric.android.controller.ContentProviderController;
-import org.robolectric.util.ReflectionHelpers;
 
 import java.util.ArrayList;
 
 /**
  * Wrap a ContentProvider, appending &test=1 to all queries.
  */
 public class DelegatingTestContentProvider extends ContentProvider {
-    protected ContentProvider mTargetProvider;
+    protected final ContentProvider mTargetProvider;
 
     protected static Uri appendUriParam(Uri uri, String param, String value) {
         return uri.buildUpon().appendQueryParameter(param, value).build();
     }
 
-    /**
-     * Create and register a new <tt>BrowserProvider</tt> that has test delegation.
-     * <p>
-     * Robolectric doesn't make it easy to parameterize a created
-     * <tt>ContentProvider</tt>, so we modify a built-in helper to do it.
-     * @return delegated <tt>ContentProvider</tt>.
-     */
-    public static ContentProvider createDelegatingBrowserProvider() {
-        final ContentProviderController<DelegatingTestContentProvider> contentProviderController
-                = ContentProviderController.of(ReflectionHelpers.callConstructor(DelegatingTestContentProvider.class,
-                ReflectionHelpers.ClassParameter.from(ContentProvider.class, new BrowserProvider())));
-        return contentProviderController.create(BrowserContract.AUTHORITY).get();
-    }
-
-    /**
-     * Create and register a new <tt>TabsProvider</tt> that has test delegation.
-     * <p>
-     * Robolectric doesn't make it easy to parameterize a created
-     * <tt>ContentProvider</tt>, so we modify a built-in helper to do it.
-     * @return delegated <tt>ContentProvider</tt>.
-     */
-    public static ContentProvider createDelegatingTabsProvider() {
-        final ContentProviderController<DelegatingTestContentProvider> contentProviderController
-                = ContentProviderController.of(ReflectionHelpers.callConstructor(DelegatingTestContentProvider.class,
-                ReflectionHelpers.ClassParameter.from(ContentProvider.class, new TabsProvider())));
-        return contentProviderController.create(BrowserContract.TABS_AUTHORITY).get();
-    }
-
     public DelegatingTestContentProvider(ContentProvider targetProvider) {
         super();
         mTargetProvider = targetProvider;
     }
 
-    public void attachInfo(Context context, ProviderInfo info) {
-        // With newer Robolectric versions, we must create the target provider
-        // before calling into super.  If we don't do this, the target
-        // provider's onCreate() will witness a null getContext(), which the
-        // Android documentation guarantees never happens on device.
-        mTargetProvider.attachInfo(context, null);
-        super.attachInfo(context, info);
-    }
-
     private Uri appendTestParam(Uri uri) {
         return appendUriParam(uri, BrowserContract.PARAM_IS_TEST, "1");
     }
 
     @Override
     public boolean onCreate() {
         return mTargetProvider.onCreate();
     }
@@ -126,16 +83,12 @@ public class DelegatingTestContentProvid
     }
 
     @Nullable
     @Override
     public Bundle call(String method, String arg, Bundle extras) {
         return mTargetProvider.call(method, arg, extras);
     }
 
-    public void shutdown() {
-        mTargetProvider.shutdown();
-    }
-
     public ContentProvider getTargetProvider() {
         return mTargetProvider;
     }
 }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/TestTabsProvider.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/db/TestTabsProvider.java
@@ -1,28 +1,28 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.background.db;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 
 import org.json.simple.JSONArray;
 import org.junit.After;
 import org.junit.Assert;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.TabsProvider;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.mozilla.gecko.sync.repositories.android.FennecTabsRepository;
 import org.mozilla.gecko.sync.repositories.domain.TabsRecord;
 import org.robolectric.shadows.ShadowContentResolver;
 
 @RunWith(TestRunner.class)
 public class TestTabsProvider {
     public static final String TEST_CLIENT_GUID = "test guid"; // Real GUIDs never contain spaces.
@@ -30,21 +30,23 @@ public class TestTabsProvider {
 
     public static final String CLIENTS_GUID_IS = BrowserContract.Clients.GUID + " = ?";
     public static final String TABS_CLIENT_GUID_IS = BrowserContract.Tabs.CLIENT_GUID + " = ?";
 
     protected Tab testTab1;
     protected Tab testTab2;
     protected Tab testTab3;
 
-    protected ContentProvider provider;
+    protected TabsProvider provider;
 
     @Before
     public void setUp() {
-        provider = DelegatingTestContentProvider.createDelegatingTabsProvider();
+        provider = new TabsProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.TABS_AUTHORITY, new DelegatingTestContentProvider(provider));
     }
 
     @After
     public void tearDown() throws Exception {
         provider.shutdown();
         provider = null;
     }
 
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockGlobalSession.java
@@ -8,32 +8,30 @@ import org.mozilla.gecko.sync.NonObjectJ
 import org.mozilla.gecko.sync.SyncConfiguration;
 import org.mozilla.gecko.sync.SyncConfigurationException;
 import org.mozilla.gecko.sync.crypto.KeyBundle;
 import org.mozilla.gecko.sync.delegates.GlobalSessionCallback;
 import org.mozilla.gecko.sync.net.BasicAuthHeaderProvider;
 import org.mozilla.gecko.sync.stage.CompletedStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage;
 import org.mozilla.gecko.sync.stage.GlobalSyncStage.Stage;
-import org.robolectric.Robolectric;
-import org.robolectric.RuntimeEnvironment;
 
 import java.io.IOException;
 import java.util.HashMap;
 
 
 public class MockGlobalSession extends MockPrefsGlobalSession {
 
   public MockGlobalSession(String username, String password, KeyBundle keyBundle, GlobalSessionCallback callback) throws SyncConfigurationException, IllegalArgumentException, NonObjectJSONException, IOException {
     this(new SyncConfiguration(username, new BasicAuthHeaderProvider(username, password), new MockSharedPreferences(), keyBundle), callback);
   }
 
   public MockGlobalSession(SyncConfiguration config, GlobalSessionCallback callback)
           throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
-    super(config, callback, RuntimeEnvironment.application, null);
+    super(config, callback, null, null);
   }
 
   @Override
   public boolean isEngineRemotelyEnabled(String engine, EngineSettings engineSettings) {
     return false;
   }
 
   @Override
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/background/testhelpers/MockPrefsGlobalSession.java
@@ -48,9 +48,14 @@ public class MockPrefsGlobalSession exte
       ClientsDataDelegate clientsDelegate)
       throws SyncConfigurationException, IllegalArgumentException, IOException, NonObjectJSONException {
 
     final SharedPreferences prefs = new MockSharedPreferences();
     final SyncConfiguration config = new SyncConfiguration(username, authHeaderProvider, prefs);
     config.syncKeyBundle = syncKeyBundle;
     return new MockPrefsGlobalSession(config, callback, context, clientsDelegate);
   }
+
+  @Override
+  public Context getContext() {
+    return null;
+  }
 }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderBookmarksTest.java
@@ -1,33 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentUris;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
-import org.mozilla.gecko.db.BrowserContract.Bookmarks;
 import org.mozilla.gecko.sync.Utils;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.robolectric.shadows.ShadowContentResolver;
 
+import org.mozilla.gecko.db.BrowserContract.Bookmarks;
+
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.List;
 import java.util.Random;
 import java.util.UUID;
 import java.util.concurrent.ConcurrentHashMap;
 
 import static org.junit.Assert.assertEquals;
@@ -36,34 +36,35 @@ import static org.junit.Assert.assertNot
 import static org.junit.Assert.assertNotNull;
 import static org.junit.Assert.assertNull;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.INVALID_TIMESTAMP;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.assertVersionsForSelection;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.bookmarksTestSyncUri;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.bookmarksTestUri;
+import static org.mozilla.gecko.db.BrowserProviderGeneralTest.getBookmarksTestSyncIncrementLocalVersionUri;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.getBookmarkIdFromGuid;
-import static org.mozilla.gecko.db.BrowserProviderGeneralTest
-        .getBookmarksTestSyncIncrementLocalVersionUri;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.insertBookmark;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.withDeleted;
 import static org.mozilla.gecko.db.BrowserProviderGeneralTest.withSync;
 
 /**
  * Testing direct interactions with bookmarks through BrowserProvider
  */
 @RunWith(TestRunner.class)
 public class BrowserProviderBookmarksTest {
     private ContentProviderClient bookmarksClient;
-    private ContentProvider provider;
+    private BrowserProvider provider;
 
     @Before
     public void setUp() throws Exception {
-        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        provider = new BrowserProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
 
         ShadowContentResolver contentResolver = new ShadowContentResolver();
         bookmarksClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
     }
 
     @After
     public void tearDown() {
         bookmarksClient.release();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderGeneralTest.java
@@ -1,20 +1,20 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
+import android.provider.Browser;
 
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
@@ -36,22 +36,25 @@ public class BrowserProviderGeneralTest 
     final static Uri bookmarksTestSyncUri = withSync(bookmarksTestUri);
     final static Uri getBookmarksTestSyncIncrementLocalVersionUri = bookmarksTestSyncUri
             .buildUpon()
             .appendQueryParameter(BrowserContract.PARAM_INCREMENT_LOCAL_VERSION_FROM_SYNC, "true")
             .build();
 
     private static final long INVALID_ID = -1;
 
-    private ContentProvider provider;
+    private BrowserProvider provider;
     private ContentProviderClient browserClient;
 
     @Before
     public void setUp() throws Exception {
-        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        provider = new BrowserProvider();
+        provider.onCreate();
+
+        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
 
         ShadowContentResolver contentResolver = new ShadowContentResolver();
         browserClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
     }
 
     @After
     public void tearDown() throws Exception {
         browserClient.release();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/BrowserProviderHistoryVisitsTestBase.java
@@ -1,36 +1,39 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.net.Uri;
 import android.os.RemoteException;
 
 import org.junit.After;
 import org.junit.Before;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.sync.repositories.android.BrowserContractHelpers;
 import org.robolectric.shadows.ShadowContentResolver;
 
+import java.util.UUID;
+
 public class BrowserProviderHistoryVisitsTestBase {
     /* package-private */ ShadowContentResolver contentResolver;
     /* package-private */ ContentProviderClient historyClient;
     /* package-private */ ContentProviderClient visitsClient;
     /* package-private */ Uri historyTestUri;
     /* package-private */ Uri visitsTestUri;
-    /* package-private */ ContentProvider provider;
+    /* package-private */ BrowserProvider provider;
 
     @Before
     public void setUp() throws Exception {
-        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        provider = new BrowserProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
 
         contentResolver = new ShadowContentResolver();
         historyClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.HISTORY_CONTENT_URI);
         visitsClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
 
         historyTestUri = testUri(BrowserContract.History.CONTENT_URI);
         visitsTestUri = testUri(BrowserContract.Visits.CONTENT_URI);
     }
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/db/LocalBrowserDBTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/db/LocalBrowserDBTest.java
@@ -1,15 +1,14 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.db;
 
 import android.annotation.SuppressLint;
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.RemoteException;
 import android.support.annotation.Nullable;
@@ -40,23 +39,25 @@ public class LocalBrowserDBTest {
     private static final String BOOKMARK_TITLE = "mozilla";
 
     private static final String UPDATE_URL = "https://bugzilla.mozilla.org";
     private static final String UPDATE_TITLE = "bugzilla";
 
     private static final String FOLDER_NAME = "folder1";
 
     private Context context;
-    private ContentProvider provider;
+    private BrowserProvider provider;
     private ContentProviderClient bookmarkClient;
 
     @Before
     public void setUp() throws Exception {
         context = RuntimeEnvironment.application;
-        provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        provider = new BrowserProvider();
+        provider.onCreate();
+        ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
 
         ShadowContentResolver contentResolver = new ShadowContentResolver();
         bookmarkClient = contentResolver.acquireContentProviderClient(BrowserContractHelpers.BOOKMARKS_CONTENT_URI);
     }
 
     @After
     public void tearDown() {
         bookmarkClient.release();
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/fxa/devices/TestFxAccountDeviceListUpdater.java
@@ -1,14 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.fxa.devices;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Bundle;
 import android.os.RemoteException;
 
@@ -17,16 +16,17 @@ import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
 import org.mockito.Mock;
 import org.mockito.MockitoAnnotations;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.fxa.FxAccountClient;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserProvider;
 import org.mozilla.gecko.fxa.authenticator.AndroidFxAccount;
 import org.mozilla.gecko.fxa.login.State;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import java.util.List;
 import java.util.UUID;
 
 import static java.util.Objects.deepEquals;
@@ -114,19 +114,22 @@ public class TestFxAccountDeviceListUpda
         assertTrue(firstDevice.getAsLong(BrowserContract.RemoteDevices.DATE_MODIFIED) < timeBeforeCall + 10000);
         assertEquals(firstDevice.getAsString(BrowserContract.RemoteDevices.NAME), device.name);
     }
 
     @Test
     public void testBrowserProvider() {
         Uri uri = testUri(BrowserContract.RemoteDevices.CONTENT_URI);
 
-        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        BrowserProvider provider = new BrowserProvider();
         Cursor c = null;
         try {
+            provider.onCreate();
+            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+
             final ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient remoteDevicesClient = cr.acquireContentProviderClient(BrowserContract.RemoteDevices.CONTENT_URI);
 
             // First let's insert a client for initial state.
 
             Bundle bundle = new Bundle();
             ContentValues device1 = createMockRemoteClientValues("device1");
             bundle.putParcelableArray(BrowserContract.METHOD_PARAM_DATA, new ContentValues[] { device1 });
--- a/mobile/android/services/src/test/java/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
+++ b/mobile/android/services/src/test/java/org/mozilla/gecko/sync/repositories/android/VisitsHelperTest.java
@@ -1,27 +1,27 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 package org.mozilla.gecko.sync.repositories.android;
 
-import android.content.ContentProvider;
 import android.content.ContentProviderClient;
 import android.content.ContentValues;
 import android.net.Uri;
 
 import junit.framework.Assert;
 
 import org.json.simple.JSONArray;
 import org.json.simple.JSONObject;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mozilla.gecko.background.db.DelegatingTestContentProvider;
 import org.mozilla.gecko.background.testhelpers.TestRunner;
 import org.mozilla.gecko.db.BrowserContract;
+import org.mozilla.gecko.db.BrowserProvider;
 import org.robolectric.shadows.ShadowContentResolver;
 
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertTrue;
 
 @RunWith(TestRunner.class)
 public class VisitsHelperTest {
     @Test
@@ -51,18 +51,21 @@ public class VisitsHelperTest {
         Assert.assertEquals(Long.valueOf(date + 1000), cv2.getAsLong(BrowserContract.Visits.DATE_VISITED));
     }
 
     @Test
     public void testGetRecentHistoryVisitsForGUID() throws Exception {
         Uri historyTestUri = testUri(BrowserContract.History.CONTENT_URI);
         Uri visitsTestUri = testUri(BrowserContract.Visits.CONTENT_URI);
 
-        final ContentProvider provider = DelegatingTestContentProvider.createDelegatingBrowserProvider();
+        BrowserProvider provider = new BrowserProvider();
         try {
+            provider.onCreate();
+            ShadowContentResolver.registerProvider(BrowserContract.AUTHORITY, new DelegatingTestContentProvider(provider));
+
             final ShadowContentResolver cr = new ShadowContentResolver();
             ContentProviderClient historyClient = cr.acquireContentProviderClient(BrowserContractHelpers.HISTORY_CONTENT_URI);
             ContentProviderClient visitsClient = cr.acquireContentProviderClient(BrowserContractHelpers.VISITS_CONTENT_URI);
 
             ContentValues historyItem = new ContentValues();
             historyItem.put(BrowserContract.History.URL, "https://www.mozilla.org");
             historyItem.put(BrowserContract.History.GUID, "testGUID");
             historyClient.insert(historyTestUri, historyItem);
--- a/mobile/android/thirdparty/build.gradle
+++ b/mobile/android/thirdparty/build.gradle
@@ -1,16 +1,20 @@
 buildDir "${topobjdir}/gradle/build/mobile/android/thirdparty"
 
 apply plugin: 'com.android.library'
 
 android {
     compileSdkVersion project.ext.compileSdkVersion
+    buildToolsVersion project.ext.buildToolsVersion
 
     defaultConfig {
+        defaultPublishConfig 'release'
+        publishNonDefault true
+
         targetSdkVersion project.ext.targetSdkVersion
         minSdkVersion project.ext.minSdkVersion
         manifestPlaceholders = project.ext.manifestPlaceholders
     }
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_7
         targetCompatibility JavaVersion.VERSION_1_7
@@ -38,21 +42,21 @@ android {
                 // here is only the no-op library for mach-based builds.
                 exclude 'com/squareup/leakcanary/**'
             }
         }
     }
 }
 
 dependencies {
-    implementation "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+    compile "com.android.support:support-v4:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
     if (mozconfig.substs.MOZ_ANDROID_MMA) {
-        implementation "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
-        implementation "com.android.support:support-annotations:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
-        implementation "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.android.support:appcompat-v7:${mozconfig.substs.ANDROID_SUPPORT_LIBRARY_VERSION}"
+        compile "com.android.support:support-annotations:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
+        compile "com.google.android.gms:play-services-gcm:${mozconfig.substs.ANDROID_GOOGLE_PLAY_SERVICES_VERSION}"
     }
 }
 
 apply plugin: 'idea'
 
 idea {
     module {
         // This is cosmetic.  See the excludes in the root project.
--- a/old-configure.in
+++ b/old-configure.in
@@ -2128,17 +2128,17 @@ AC_SUBST(MOZ_MULET)
 
 dnl ========================================================
 dnl Ensure Android SDK and build-tools versions depending on
 dnl mobile target.
 dnl ========================================================
 
 case "$MOZ_BUILD_APP" in
 mobile/android)
-    MOZ_ANDROID_SDK(23, 23, 26.0.2, 26.0.0 26.0.0-dev 25.3.2 25.3.1)
+    MOZ_ANDROID_SDK(23, 23, 25.0.3, 26.0.0 26.0.0-dev 25.3.2 25.3.1)
     ;;
 esac
 
 dnl ========================================================
 dnl =
 dnl = Toolkit Options
 dnl =
 dnl ========================================================
--- a/python/mozboot/mozboot/android-packages.txt
+++ b/python/mozboot/mozboot/android-packages.txt
@@ -1,6 +1,6 @@
 platform-tools
-build-tools;26.0.2
+build-tools;25.0.3
 platforms;android-23
 extras;android;m2repository
 extras;google;m2repository
 emulator
--- a/python/mozbuild/mozbuild/config_status.py
+++ b/python/mozbuild/mozbuild/config_status.py
@@ -33,20 +33,23 @@ from mozbuild.backend import (
 log_manager = LoggingManager()
 
 
 ANDROID_IDE_ADVERTISEMENT = '''
 =============
 ADVERTISEMENT
 
 You are building Firefox for Android. After your build completes, you can open
-the top source directory in Android Studio directly and build using Gradle.
-See the documentation at
+the top source directory in IntelliJ or Android Studio directly and build using
+Gradle.  See the documentation at
 
 https://developer.mozilla.org/en-US/docs/Simple_Firefox_for_Android_build
+
+PLEASE BE AWARE THAT GRADLE AND INTELLIJ/ANDROID STUDIO SUPPORT IS EXPERIMENTAL.
+You should verify any changes using |mach build|.
 =============
 '''.strip()
 
 VISUAL_STUDIO_ADVERTISEMENT = '''
 ===============================
 Visual Studio Support Available
 
 You are building Firefox on Windows. You can generate Visual Studio
--- a/taskcluster/ci/build/android.yml
+++ b/taskcluster/ci/build/android.yml
@@ -16,17 +16,17 @@ android-api-16/debug:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -68,17 +68,17 @@ android-x86/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -125,17 +125,17 @@ android-x86-nightly/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -173,17 +173,17 @@ android-api-16/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -225,17 +225,17 @@ android-api-16-nightly/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -278,17 +278,17 @@ android-x86-old-id/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -334,17 +334,17 @@ android-x86-old-id-nightly/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -383,17 +383,17 @@ android-api-16-old-id/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -434,17 +434,17 @@ android-api-16-old-id-nightly/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -483,17 +483,17 @@ android-api-16-gradle/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -529,17 +529,17 @@ android-aarch64/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
@@ -581,17 +581,17 @@ android-aarch64-nightly/opt:
         artifacts:
             - name: public/android/R
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/app/R
               type: directory
             - name: public/android/maven
               path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview/maven/
               type: directory
             - name: public/build/geckoview_example.apk
-              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/officialWithGeckoBinariesNoMinApi/debug/geckoview_example-official-withGeckoBinaries-noMinApi-debug.apk
+              path: /builds/worker/workspace/build/src/obj-firefox/gradle/build/mobile/android/geckoview_example/outputs/apk/geckoview_example-withGeckoBinaries.apk
               type: file
             - name: public/build
               path: /builds/worker/artifacts/
               type: directory
     run:
         using: mozharness
         actions: [get-secrets build multi-l10n update]
         config:
--- a/taskcluster/scripts/misc/android-gradle-dependencies/after.sh
+++ b/taskcluster/scripts/misc/android-gradle-dependencies/after.sh
@@ -1,16 +1,16 @@
 #!/bin/bash -vex
 
 set -x -e
 
 echo "running as" $(id)
 
 : WORKSPACE ${WORKSPACE:=/builds/worker/workspace}
-: GRADLE_VERSION ${GRADLE_VERSION:=4.1}
+: GRADLE_VERSION ${GRADLE_VERSION:=3.4.1}
 
 set -v
 
 # Package everything up.
 pushd $WORKSPACE
 mkdir -p android-gradle-dependencies /builds/worker/artifacts
 
 cp -R ${NEXUS_WORK}/storage/jcenter android-gradle-dependencies
--- a/taskcluster/scripts/misc/android-gradle-dependencies/nexus.xml
+++ b/taskcluster/scripts/misc/android-gradle-dependencies/nexus.xml
@@ -67,18 +67,17 @@
       <localStorage>
         <provider>file</provider>
       </localStorage>
       <remoteStorage>
         <url>https://maven.google.com/</url>
       </remoteStorage>
       <externalConfiguration>
         <repositoryPolicy>RELEASE</repositoryPolicy>
-        <!-- Google doesn't publish checksums.  Why, Google, why? -->
-        <checksumPolicy>STRICT_IF_EXISTS</checksumPolicy>
+        <checksumPolicy>STRICT</checksumPolicy>
         <fileTypeValidation>true</fileTypeValidation>
         <downloadRemoteIndex>false</downloadRemoteIndex>
         <artifactMaxAge>-1</artifactMaxAge>
         <metadataMaxAge>1440</metadataMaxAge>
         <itemMaxAge>1440</itemMaxAge>
         <autoBlockActive>true</autoBlockActive>
       </externalConfiguration>
     </repository>