Merge mozilla-central to mozilla-inbound
authorCarsten "Tomcat" Book <cbook@mozilla.com>
Fri, 14 Nov 2014 13:23:00 +0100
changeset 215760 da57927b609dc71e13b14fd1d4701cb2d59f0dfd
parent 215711 8fee3f35e2ba59ab8af2e60576715304430324b5 (current diff)
parent 215759 64206634959a2e84eefec40d1da0122c7a63bc20 (diff)
child 215761 fa836f2dccb65885741e9195c6650625d4ad373b
push id51845
push usercbook@mozilla.com
push dateFri, 14 Nov 2014 12:23:21 +0000
treeherdermozilla-inbound@da57927b609d [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone36.0a1
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
Merge mozilla-central to mozilla-inbound
--- a/CLOBBER
+++ b/CLOBBER
@@ -17,9 +17,9 @@
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
 # Are you updating CLOBBER because you think it's needed for your WebIDL
 # changes to stick? As of bug 928195, this shouldn't be necessary! Please
 # don't change CLOBBER for WebIDL changes any more.
 
-Bug 1095234 - Bug 1091260 stopped packaging a devtools file with EXTRA_JS_MODULES while making it require pre-processing.
+Bug 1084498 - Android build tools dependency.
--- a/b2g/config/dolphin/sources.xml
+++ b/b2g/config/dolphin/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
--- a/b2g/config/emulator-ics/sources.xml
+++ b/b2g/config/emulator-ics/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/emulator-jb/sources.xml
+++ b/b2g/config/emulator-jb/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/emulator-kk/sources.xml
+++ b/b2g/config/emulator-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
--- a/b2g/config/emulator/sources.xml
+++ b/b2g/config/emulator/sources.xml
@@ -14,17 +14,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="refs/tags/android-4.0.4_r2.1" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="cd88d860656c31c7da7bb310d6a160d0011b0961"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="67f2907bc340bad250b4ea6ce2902b52896c9ef0"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame-kk/sources.xml
+++ b/b2g/config/flame-kk/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was git://codeaurora.org/-->
   <remote fetch="https://git.mozilla.org/external/caf" name="caf"/>
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="3ab0d9c70f0b2e1ededc679112c392303f037361">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="e95b4ce22c825da44d14299e1190ea39a5260bde"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="471afab478649078ad7c75ec6b252481a59e19b8"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "26b4d06af9d942c3b6ab81f119d33951823bc256", 
+    "revision": "806fe1b93ad83755a081391113215543c078e19c", 
     "repo_path": "integration/gaia-central"
 }
--- a/b2g/config/hamachi/sources.xml
+++ b/b2g/config/hamachi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="746bc48f34f5060f90801925dcdd964030c1ab6d"/>
--- a/b2g/config/helix/sources.xml
+++ b/b2g/config/helix/sources.xml
@@ -10,17 +10,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <default remote="caf" revision="b2g/ics_strawberry" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- a/b2g/config/nexus-4/sources.xml
+++ b/b2g/config/nexus-4/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was https://git.mozilla.org/releases-->
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="0e94c080bee081a50aa2097527b0b40852f9143f">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <project name="valgrind" path="external/valgrind" remote="b2g" revision="daa61633c32b9606f58799a3186395fd2bbb8d8c"/>
   <project name="vex" path="external/VEX" remote="b2g" revision="47f031c320888fe9f3e656602588565b52d43010"/>
   <!-- Stock Android things -->
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.1" path="prebuilts/clang/linux-x86/3.1" revision="5c45f43419d5582949284eee9cef0c43d866e03b"/>
   <project groups="linux" name="platform/prebuilts/clang/linux-x86/3.2" path="prebuilts/clang/linux-x86/3.2" revision="3748b4168e7bd8d46457d4b6786003bc6a5223ce"/>
--- a/b2g/config/wasabi/sources.xml
+++ b/b2g/config/wasabi/sources.xml
@@ -12,17 +12,17 @@
   <!--original fetch url was git://github.com/apitrace/-->
   <remote fetch="https://git.mozilla.org/external/apitrace" name="apitrace"/>
   <default remote="caf" revision="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="df362ace56338da8173d30d3e09e08c42c1accfa">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="fake-dalvik" path="dalvik" remote="b2g" revision="ca1f327d5acc198bb4be62fa51db2c039032c9ce"/>
-  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="c4f85f8825f0c1795fcd9152185c48f4f2eee9d6"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="1e300eac2e56d98ad51d414766d031db7d33221f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="45c54a55e31758f7e54e5eafe0d01d387f35897a"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="librecovery" path="librecovery" remote="b2g" revision="891e5069c0ad330d8191bf8c7b879c814258c89f"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="562d357b72279a9e35d4af5aeecc8e1ffa2f44f1"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="0494efbb157e863dec6f1fd4d4652b0917ce263d"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/b2g/installer/package-manifest.in
+++ b/b2g/installer/package-manifest.in
@@ -444,16 +444,18 @@
 @BINPATH@/components/MmsService.manifest
 @BINPATH@/components/MobileConnectionService.js
 @BINPATH@/components/MobileConnectionService.manifest
 @BINPATH@/components/MobileMessageDatabaseService.js
 @BINPATH@/components/MobileMessageDatabaseService.manifest
 @BINPATH@/components/RadioInterfaceLayer.js
 @BINPATH@/components/RadioInterfaceLayer.manifest
 @BINPATH@/components/RILContentHelper.js
+@BINPATH@/components/TelephonyAudioService.js
+@BINPATH@/components/TelephonyAudioService.manifest
 @BINPATH@/components/TelephonyService.js
 @BINPATH@/components/TelephonyService.manifest
 @BINPATH@/components/VoicemailService.js
 @BINPATH@/components/VoicemailService.manifest
 #endif // MOZ_WIDGET_GONK && MOZ_B2G_RIL
 
 #ifndef MOZ_WIDGET_GONK
 @BINPATH@/components/extensions.manifest
--- a/browser/components/loop/content/shared/css/conversation.css
+++ b/browser/components/loop/content/shared/css/conversation.css
@@ -692,16 +692,48 @@ html, .fx-embedded, #main,
  * Rooms
  */
 
 .room-conversation-wrapper {
   position: relative;
   height: 100%;
 }
 
+.standalone .room-conversation-wrapper {
+  height: calc(100% - 50px - 60px);
+  background: #000;
+}
+
+.room-conversation-wrapper header {
+  background: #000;
+  height: 50px;
+  text-align: left;
+}
+
+.room-conversation-wrapper header h1 {
+  font-size: 1.5em;
+  color: #fff;
+  line-height: 50px;
+  text-indent: 50px;
+  background-image: url("../img/firefox-logo.png");
+  background-size: 30px;
+  background-position: 10px;
+  background-repeat: no-repeat;
+}
+
+.room-conversation-wrapper footer {
+  background: #000;
+  height: 60px;
+  margin-top: -12px;
+}
+
+.room-conversation-wrapper footer a {
+  color: #555;
+}
+
 /**
  * Hides the hangup button for room conversations.
  */
 .room-conversation .conversation-toolbar .btn-hangup-entry {
   display: none;
 }
 
 .room-invitation-overlay {
@@ -740,17 +772,17 @@ html, .fx-embedded, #main,
 /* Standalone rooms */
 
 .standalone .room-conversation-wrapper {
   position: relative;
 }
 
 .standalone .room-inner-info-area {
   position: absolute;
-  top: 35%;
+  top: 50%;
   left: 0;
   right: 25%;
   z-index: 1000;
   margin: 0 auto;
   width: 50%;
   color: #fff;
   font-weight: bold;
   font-size: 1.1em;
@@ -762,16 +794,17 @@ html, .fx-embedded, #main,
   padding: .2em 1.2em;
   cursor: pointer;
 }
 
 .standalone .room-inner-info-area a.btn {
   padding: .5em 3em .3em 3em;
   border-radius: 3px;
   font-weight: normal;
+  max-width: 400px;
 }
 
 .standalone .room-conversation h2.room-name {
   position: absolute;
   display: inline-block;
   top: 0;
   right: 0;
   color: #fff;
@@ -791,14 +824,14 @@ html, .fx-embedded, #main,
 
 .standalone .room-conversation .local-stream {
   width: 33%;
   height: 26.5%;
 }
 
 .standalone .room-conversation .conversation-toolbar {
   background: #000;
-  border-top: none;
+  border: none;
 }
 
 .standalone .room-conversation .conversation-toolbar .btn-hangup-entry {
   display: block;
 }
--- a/browser/components/loop/content/shared/js/activeRoomStore.js
+++ b/browser/components/loop/content/shared/js/activeRoomStore.js
@@ -5,16 +5,25 @@
 /* global loop:true */
 
 var loop = loop || {};
 loop.store = loop.store || {};
 loop.store.ActiveRoomStore = (function() {
   "use strict";
 
   var sharedActions = loop.shared.actions;
+  var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
+
+  // Error numbers taken from
+  // https://github.com/mozilla-services/loop-server/blob/master/loop/errno.json
+  var SERVER_CODES = loop.store.SERVER_CODES = {
+    INVALID_TOKEN: 105,
+    EXPIRED: 111,
+    ROOM_FULL: 202
+  };
 
   var ROOM_STATES = loop.store.ROOM_STATES = {
     // The initial state of the room
     INIT: "room-init",
     // The store is gathering the room data
     GATHER: "room-gather",
     // The store has got the room data
     READY: "room-ready",
@@ -79,17 +88,18 @@ loop.store.ActiveRoomStore = (function()
      * @property {ROOM_STATES} roomState - the state of the room.
      * @property {Error=} error - if the room is an error state, this will be
      *                            set to an Error object reflecting the problem;
      *                            otherwise it will be unset.
      */
     this._storeState = {
       roomState: ROOM_STATES.INIT,
       audioMuted: false,
-      videoMuted: false
+      videoMuted: false,
+      failureReason: undefined
     };
   }
 
   ActiveRoomStore.prototype = _.extend({
     /**
      * The time factor to adjust the expires time to ensure that we send a refresh
      * before the expiry. Currently set as 90%.
      */
@@ -107,23 +117,34 @@ loop.store.ActiveRoomStore = (function()
     },
 
     /**
      * Handles a room failure.
      *
      * @param {sharedActions.RoomFailure} actionData
      */
     roomFailure: function(actionData) {
+      function getReason(serverCode) {
+        switch (serverCode) {
+          case SERVER_CODES.INVALID_TOKEN:
+          case SERVER_CODES.EXPIRED:
+            return FAILURE_REASONS.EXPIRED_OR_INVALID;
+          default:
+            return FAILURE_REASONS.UNKNOWN;
+        }
+      }
+
       console.error("Error in state `" + this._storeState.roomState + "`:",
         actionData.error);
 
       this.setStoreState({
         error: actionData.error,
-        roomState: actionData.error.errno === 202 ? ROOM_STATES.FULL
-                                                  : ROOM_STATES.FAILED
+        failureReason: getReason(actionData.error.errno),
+        roomState: actionData.error.errno === SERVER_CODES.ROOM_FULL ?
+          ROOM_STATES.FULL : ROOM_STATES.FAILED
       });
     },
 
     /**
      * Registers the actions with the dispatcher that this store is interested
      * in.
      */
     _registerActions: function() {
@@ -223,16 +244,21 @@ loop.store.ActiveRoomStore = (function()
         roomUrl: actionData.roomUrl
       });
     },
 
     /**
      * Handles the action to join to a room.
      */
     joinRoom: function() {
+      // Reset the failure reason if necessary.
+      if (this.getStoreState().failureReason) {
+        this.setStoreState({failureReason: undefined});
+      }
+
       this._mozLoop.rooms.join(this._storeState.roomToken,
         function(error, responseData) {
           if (error) {
             this._dispatcher.dispatch(
               new sharedActions.RoomFailure({error: error}));
             return;
           }
 
@@ -270,21 +296,27 @@ loop.store.ActiveRoomStore = (function()
     connectedToSdkServers: function() {
       this.setStoreState({
         roomState: ROOM_STATES.SESSION_CONNECTED
       });
     },
 
     /**
      * Handles disconnection of this local client from the sdk servers.
+     *
+     * @param {sharedActions.ConnectionFailure} actionData
      */
-    connectionFailure: function() {
+    connectionFailure: function(actionData) {
       // Treat all reasons as something failed. In theory, clientDisconnected
       // could be a success case, but there's no way we should be intentionally
       // sending that and still have the window open.
+      this.setStoreState({
+        failureReason: actionData.reason
+      });
+
       this._leaveRoom(ROOM_STATES.FAILED);
     },
 
     /**
      * Records the mute state for the stream.
      *
      * @param {sharedActions.setMute} actionData The mute state for the stream type.
      */
--- a/browser/components/loop/content/shared/js/otSdkDriver.js
+++ b/browser/components/loop/content/shared/js/otSdkDriver.js
@@ -3,16 +3,17 @@
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 /* global loop:true */
 
 var loop = loop || {};
 loop.OTSdkDriver = (function() {
 
   var sharedActions = loop.shared.actions;
+  var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
 
   /**
    * This is a wrapper for the OT sdk. It is used to translate the SDK events into
    * actions, and instruct the SDK what to do as a result of actions.
    */
   var OTSdkDriver = function(options) {
       if (!options.dispatcher) {
         throw new Error("Missing option dispatcher");
@@ -42,18 +43,21 @@ loop.OTSdkDriver = (function() {
       this.getLocalElement = actionData.getLocalElementFunc;
       this.getRemoteElement = actionData.getRemoteElementFunc;
       this.publisherConfig = actionData.publisherConfig;
 
       // At this state we init the publisher, even though we might be waiting for
       // the initial connect of the session. This saves time when setting up
       // the media.
       this.publisher = this.sdk.initPublisher(this.getLocalElement(),
-        this.publisherConfig,
-        this._onPublishComplete.bind(this));
+        this.publisherConfig);
+      this.publisher.on("accessAllowed", this._onPublishComplete.bind(this));
+      this.publisher.on("accessDenied", this._onPublishDenied.bind(this));
+      this.publisher.on("accessDialogOpened",
+        this._onAccessDialogOpened.bind(this));
     },
 
     /**
      * Handles the setMute action. Informs the published stream to mute
      * or unmute audio as appropriate.
      *
      * @param {sharedActions.SetMute} actionData The data associated with the
      *                                           action. See action.js.
@@ -91,26 +95,22 @@ loop.OTSdkDriver = (function() {
         this._onConnectionComplete.bind(this));
     },
 
     /**
      * Disconnects the sdk session.
      */
     disconnectSession: function() {
       if (this.session) {
-        this.session.off("streamCreated", this._onRemoteStreamCreated.bind(this));
-        this.session.off("connectionDestroyed",
-          this._onConnectionDestroyed.bind(this));
-        this.session.off("sessionDisconnected",
-          this._onSessionDisconnected.bind(this));
-
+        this.session.off("streamCreated connectionDestroyed sessionDisconnected");
         this.session.disconnect();
         delete this.session;
       }
       if (this.publisher) {
+        this.publisher.off("accessAllowed accessDenied accessDialogOpened");
         this.publisher.destroy();
         delete this.publisher;
       }
 
       // Also, tidy these variables ready for next time.
       delete this._sessionConnected;
       delete this._publisherReady;
       delete this._publishedLocalStream;
@@ -121,17 +121,17 @@ loop.OTSdkDriver = (function() {
      * Called once the session has finished connecting.
      *
      * @param {Error} error An OT error object, null if there was no error.
      */
     _onConnectionComplete: function(error) {
       if (error) {
         console.error("Failed to complete connection", error);
         this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
-          reason: "couldNotConnect"
+          reason: FAILURE_REASONS.COULD_NOT_CONNECT
         }));
         return;
       }
 
       this.dispatcher.dispatch(new sharedActions.ConnectedToSdkServers());
       this._sessionConnected = true;
       this._maybePublishLocalStream();
     },
@@ -154,17 +154,17 @@ loop.OTSdkDriver = (function() {
      *
      * @param {SessionDisconnectEvent} event The event details:
      * https://tokbox.com/opentok/libraries/client/js/reference/SessionDisconnectEvent.html
      */
     _onSessionDisconnected: function(event) {
       // We only need to worry about the network disconnected reason here.
       if (event.reason === "networkDisconnected") {
         this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
-          reason: "networkDisconnected"
+          reason: FAILURE_REASONS.NETWORK_DISCONNECTED
         }));
       }
     },
 
     _onConnectionCreated: function(event) {
       if (this.session.connection.id === event.connection.id) {
         return;
       }
@@ -184,34 +184,52 @@ loop.OTSdkDriver = (function() {
 
       this._subscribedRemoteStream = true;
       if (this._checkAllStreamsConnected()) {
         this.dispatcher.dispatch(new sharedActions.MediaConnected());
       }
     },
 
     /**
+     * Called from the sdk when the media access dialog is opened.
+     * Prevents the default action, to prevent the SDK's "allow access"
+     * dialog from being shown.
+     *
+     * @param {OT.Event} event
+     */
+    _onAccessDialogOpened: function(event) {
+      event.preventDefault();
+    },
+
+    /**
      * Handles the publishing being complete.
      *
-     * @param {Error} error An OT error object, null if there was no error.
+     * @param {OT.Event} event
      */
-    _onPublishComplete: function(error) {
-      if (error) {
-        console.error("Failed to initialize publisher", error);
-        this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
-          reason: "noMedia"
-        }));
-        return;
-      }
-
+    _onPublishComplete: function(event) {
+      event.preventDefault();
       this._publisherReady = true;
       this._maybePublishLocalStream();
     },
 
     /**
+     * Handles publishing of media being denied.
+     *
+     * @param {OT.Event} event
+     */
+    _onPublishDenied: function(event) {
+      // This prevents the SDK's "access denied" dialog showing.
+      event.preventDefault();
+
+      this.dispatcher.dispatch(new sharedActions.ConnectionFailure({
+        reason: FAILURE_REASONS.MEDIA_DENIED
+      }));
+    },
+
+    /**
      * Publishes the local stream if the session is connected
      * and the publisher is ready.
      */
     _maybePublishLocalStream: function() {
       if (this._sessionConnected && this._publisherReady) {
         // We are clear to publish the stream to the session.
         this.session.publish(this.publisher);
 
--- a/browser/components/loop/content/shared/js/utils.js
+++ b/browser/components/loop/content/shared/js/utils.js
@@ -12,16 +12,24 @@ loop.shared.utils = (function(mozL10n) {
   /**
    * Call types used for determining if a call is audio/video or audio-only.
    */
   var CALL_TYPES = {
     AUDIO_VIDEO: "audio-video",
     AUDIO_ONLY: "audio"
   };
 
+  var FAILURE_REASONS = {
+    MEDIA_DENIED: "reason-media-denied",
+    COULD_NOT_CONNECT: "reason-could-not-connect",
+    NETWORK_DISCONNECTED: "reason-network-disconnected",
+    EXPIRED_OR_INVALID: "reason-expired-or-invalid",
+    UNKNOWN: "reason-unknown"
+  };
+
   /**
    * Format a given date into an l10n-friendly string.
    *
    * @param {Integer} The timestamp in seconds to format.
    * @return {String} The formatted string.
    */
   function formatDate(timestamp) {
     var date = (new Date(timestamp * 1000));
@@ -105,14 +113,15 @@ loop.shared.utils = (function(mozL10n) {
         learnMoreUrl: navigator.mozLoop.getLoopCharPref("learnMoreUrl")
       }),
       recipient
     );
   }
 
   return {
     CALL_TYPES: CALL_TYPES,
+    FAILURE_REASONS: FAILURE_REASONS,
     Helper: Helper,
     composeCallUrlEmail: composeCallUrlEmail,
     formatDate: formatDate,
     getBoolPreference: getBoolPreference
   };
 })(document.mozL10n || navigator.mozL10n);
--- a/browser/components/loop/standalone/content/css/webapp.css
+++ b/browser/components/loop/standalone/content/css/webapp.css
@@ -13,16 +13,23 @@ body,
 .standalone {
   width: 100%;
   background: #fbfbfb;
   color: #666;
   text-align: center;
   font-family: Open Sans,sans-serif;
 }
 
+/**
+ * Note: the is-standalone-room class is dynamically set by the StandaloneRoomView.
+ */
+.standalone.is-standalone-room {
+  background-color: #000;
+}
+
 .standalone-header {
   border-radius: 4px;
   background: #fff;
   border: 1px solid #E7E7E7;
   box-shadow: 0px 2px 0px rgba(0, 0, 0, 0.03);
   background-image: url("../shared/img/beta-ribbon.svg#beta-ribbon");
   background-size: 5rem 5rem;
   background-repeat: no-repeat;
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.js
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.js
@@ -6,18 +6,20 @@
 
 /* global loop:true, React */
 /* jshint newcap:false, maxlen:false */
 
 var loop = loop || {};
 loop.standaloneRoomViews = (function(mozL10n) {
   "use strict";
 
+  var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({displayName: 'StandaloneRoomInfoArea',
     propTypes: {
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
     _renderCallToActionLink: function() {
@@ -34,16 +36,30 @@ loop.standaloneRoomViews = (function(moz
         React.DOM.a({href: loop.config.brandWebsiteUrl, className: "btn btn-info"}, 
           mozL10n.get("rooms_room_full_call_to_action_nonFx_label", {
             brandShortname: mozL10n.get("brandShortname")
           })
         )
       );
     },
 
+    /**
+     * @return String An appropriate string according to the failureReason.
+     */
+    _getFailureString: function() {
+      switch(this.props.failureReason) {
+        case FAILURE_REASONS.MEDIA_DENIED:
+          return mozL10n.get("rooms_media_denied_message");
+        case FAILURE_REASONS.EXPIRED_OR_INVALID:
+          return mozL10n.get("rooms_unavailable_notification_message");
+        default:
+          return mozL10n.get("status_error");
+      };
+    },
+
     _renderContent: function() {
       switch(this.props.roomState) {
         case ROOM_STATES.INIT:
         case ROOM_STATES.READY: {
           return (
             React.DOM.button({className: "btn btn-join btn-info", 
                     onClick: this.props.joinRoom}, 
               mozL10n.get("rooms_room_join_label")
@@ -62,30 +78,73 @@ loop.standaloneRoomViews = (function(moz
           return (
             React.DOM.div(null, 
               React.DOM.p({className: "full-room-message"}, 
                 mozL10n.get("rooms_room_full_label")
               ), 
               React.DOM.p(null, this._renderCallToActionLink())
             )
           );
+        case ROOM_STATES.FAILED:
+          return (
+            React.DOM.p({className: "failed-room-message"}, 
+              this._getFailureString()
+            )
+          );
         default:
           return null;
       }
     },
 
     render: function() {
       return (
         React.DOM.div({className: "room-inner-info-area"}, 
           this._renderContent()
         )
       );
     }
   });
 
+  var StandaloneRoomHeader = React.createClass({displayName: 'StandaloneRoomHeader',
+    render: function() {
+      return (
+        React.DOM.header(null, 
+          React.DOM.h1(null, mozL10n.get("clientShortname2"))
+        )
+      );
+    }
+  });
+
+  var StandaloneRoomFooter = React.createClass({displayName: 'StandaloneRoomFooter',
+    _getContent: function() {
+      return mozL10n.get("legal_text_and_links", {
+        "clientShortname": mozL10n.get("clientShortname2"),
+        "terms_of_use_url": React.renderComponentToStaticMarkup(
+          React.DOM.a({href: loop.config.legalWebsiteUrl, target: "_blank"}, 
+            mozL10n.get("terms_of_use_link_text")
+          )
+        ),
+        "privacy_notice_url": React.renderComponentToStaticMarkup(
+          React.DOM.a({href: loop.config.privacyWebsiteUrl, target: "_blank"}, 
+            mozL10n.get("privacy_notice_link_text")
+          )
+        ),
+      });
+    },
+
+    render: function() {
+      return (
+        React.DOM.footer(null, 
+          React.DOM.p({dangerouslySetInnerHTML: {__html: this._getContent()}}), 
+          React.DOM.div({className: "footer-logo"})
+        )
+      );
+    }
+  });
+
   var StandaloneRoomView = React.createClass({displayName: 'StandaloneRoomView',
     mixins: [Backbone.Events],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
@@ -139,16 +198,21 @@ loop.standaloneRoomViews = (function(moz
           bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
+    componentDidMount: function() {
+      // Adding a class to the document body element from here to ease styling it.
+      document.body.classList.add("is-standalone-room");
+    },
+
     componentWillUnmount: function() {
       this.stopListening(this.props.activeRoomStore);
     },
 
     /**
      * Watches for when we transition from READY to JOINED room state, so we can
      * request user media access.
      * @param  {Object} nextProps (Unused)
@@ -202,17 +266,19 @@ loop.standaloneRoomViews = (function(moz
         hide: !this._roomIsActive(),
         local: true,
         "local-stream": true,
         "local-stream-audio": false
       });
 
       return (
         React.DOM.div({className: "room-conversation-wrapper"}, 
+          StandaloneRoomHeader(null), 
           StandaloneRoomInfoArea({roomState: this.state.roomState, 
+                                  failureReason: this.state.failureReason, 
                                   joinRoom: this.joinRoom, 
                                   helper: this.props.helper}), 
           React.DOM.div({className: "video-layout-wrapper"}, 
             React.DOM.div({className: "conversation room-conversation"}, 
               React.DOM.h2({className: "room-name"}, this.state.roomName), 
               React.DOM.div({className: "media nested"}, 
                 React.DOM.div({className: "video_wrapper remote_wrapper"}, 
                   React.DOM.div({className: "video_inner remote"})
@@ -224,17 +290,18 @@ loop.standaloneRoomViews = (function(moz
                         visible: this._roomIsActive()}, 
                 audio: {enabled: !this.state.audioMuted,
                         visible: this._roomIsActive()}, 
                 publishStream: this.publishStream, 
                 hangup: this.leaveRoom, 
                 hangupButtonLabel: mozL10n.get("rooms_leave_button_label"), 
                 enableHangup: this._roomIsActive()})
             )
-          )
+          ), 
+          StandaloneRoomFooter(null)
         )
       );
     }
   });
 
   return {
     StandaloneRoomView: StandaloneRoomView
   };
--- a/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
+++ b/browser/components/loop/standalone/content/js/standaloneRoomViews.jsx
@@ -6,18 +6,20 @@
 
 /* global loop:true, React */
 /* jshint newcap:false, maxlen:false */
 
 var loop = loop || {};
 loop.standaloneRoomViews = (function(mozL10n) {
   "use strict";
 
+  var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
   var ROOM_STATES = loop.store.ROOM_STATES;
   var sharedActions = loop.shared.actions;
+  var sharedMixins = loop.shared.mixins;
   var sharedViews = loop.shared.views;
 
   var StandaloneRoomInfoArea = React.createClass({
     propTypes: {
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
     },
 
     _renderCallToActionLink: function() {
@@ -34,16 +36,30 @@ loop.standaloneRoomViews = (function(moz
         <a href={loop.config.brandWebsiteUrl} className="btn btn-info">
           {mozL10n.get("rooms_room_full_call_to_action_nonFx_label", {
             brandShortname: mozL10n.get("brandShortname")
           })}
         </a>
       );
     },
 
+    /**
+     * @return String An appropriate string according to the failureReason.
+     */
+    _getFailureString: function() {
+      switch(this.props.failureReason) {
+        case FAILURE_REASONS.MEDIA_DENIED:
+          return mozL10n.get("rooms_media_denied_message");
+        case FAILURE_REASONS.EXPIRED_OR_INVALID:
+          return mozL10n.get("rooms_unavailable_notification_message");
+        default:
+          return mozL10n.get("status_error");
+      };
+    },
+
     _renderContent: function() {
       switch(this.props.roomState) {
         case ROOM_STATES.INIT:
         case ROOM_STATES.READY: {
           return (
             <button className="btn btn-join btn-info"
                     onClick={this.props.joinRoom}>
               {mozL10n.get("rooms_room_join_label")}
@@ -62,30 +78,73 @@ loop.standaloneRoomViews = (function(moz
           return (
             <div>
               <p className="full-room-message">
                 {mozL10n.get("rooms_room_full_label")}
               </p>
               <p>{this._renderCallToActionLink()}</p>
             </div>
           );
+        case ROOM_STATES.FAILED:
+          return (
+            <p className="failed-room-message">
+              {this._getFailureString()}
+            </p>
+          );
         default:
           return null;
       }
     },
 
     render: function() {
       return (
         <div className="room-inner-info-area">
           {this._renderContent()}
         </div>
       );
     }
   });
 
+  var StandaloneRoomHeader = React.createClass({
+    render: function() {
+      return (
+        <header>
+          <h1>{mozL10n.get("clientShortname2")}</h1>
+        </header>
+      );
+    }
+  });
+
+  var StandaloneRoomFooter = React.createClass({
+    _getContent: function() {
+      return mozL10n.get("legal_text_and_links", {
+        "clientShortname": mozL10n.get("clientShortname2"),
+        "terms_of_use_url": React.renderComponentToStaticMarkup(
+          <a href={loop.config.legalWebsiteUrl} target="_blank">
+            {mozL10n.get("terms_of_use_link_text")}
+          </a>
+        ),
+        "privacy_notice_url": React.renderComponentToStaticMarkup(
+          <a href={loop.config.privacyWebsiteUrl} target="_blank">
+            {mozL10n.get("privacy_notice_link_text")}
+          </a>
+        ),
+      });
+    },
+
+    render: function() {
+      return (
+        <footer>
+          <p dangerouslySetInnerHTML={{__html: this._getContent()}}></p>
+          <div className="footer-logo" />
+        </footer>
+      );
+    }
+  });
+
   var StandaloneRoomView = React.createClass({
     mixins: [Backbone.Events],
 
     propTypes: {
       activeRoomStore:
         React.PropTypes.instanceOf(loop.store.ActiveRoomStore).isRequired,
       dispatcher: React.PropTypes.instanceOf(loop.Dispatcher).isRequired,
       helper: React.PropTypes.instanceOf(loop.shared.utils.Helper).isRequired
@@ -139,16 +198,21 @@ loop.standaloneRoomViews = (function(moz
           bugDisplayMode: "off",
           buttonDisplayMode: "off",
           nameDisplayMode: "off",
           videoDisabledDisplayMode: "off"
         }
       };
     },
 
+    componentDidMount: function() {
+      // Adding a class to the document body element from here to ease styling it.
+      document.body.classList.add("is-standalone-room");
+    },
+
     componentWillUnmount: function() {
       this.stopListening(this.props.activeRoomStore);
     },
 
     /**
      * Watches for when we transition from READY to JOINED room state, so we can
      * request user media access.
      * @param  {Object} nextProps (Unused)
@@ -202,17 +266,19 @@ loop.standaloneRoomViews = (function(moz
         hide: !this._roomIsActive(),
         local: true,
         "local-stream": true,
         "local-stream-audio": false
       });
 
       return (
         <div className="room-conversation-wrapper">
+          <StandaloneRoomHeader />
           <StandaloneRoomInfoArea roomState={this.state.roomState}
+                                  failureReason={this.state.failureReason}
                                   joinRoom={this.joinRoom}
                                   helper={this.props.helper} />
           <div className="video-layout-wrapper">
             <div className="conversation room-conversation">
               <h2 className="room-name">{this.state.roomName}</h2>
               <div className="media nested">
                 <div className="video_wrapper remote_wrapper">
                   <div className="video_inner remote"></div>
@@ -225,16 +291,17 @@ loop.standaloneRoomViews = (function(moz
                 audio={{enabled: !this.state.audioMuted,
                         visible: this._roomIsActive()}}
                 publishStream={this.publishStream}
                 hangup={this.leaveRoom}
                 hangupButtonLabel={mozL10n.get("rooms_leave_button_label")}
                 enableHangup={this._roomIsActive()} />
             </div>
           </div>
+          <StandaloneRoomFooter />
         </div>
       );
     }
   });
 
   return {
     StandaloneRoomView: StandaloneRoomView
   };
--- a/browser/components/loop/standalone/content/l10n/loop.en-US.properties
+++ b/browser/components/loop/standalone/content/l10n/loop.en-US.properties
@@ -111,16 +111,18 @@ rooms_new_room_button_label=Start a conv
 rooms_only_occupant_label=You're the first one here.
 rooms_panel_title=Choose a conversation or start a new one
 rooms_room_full_label=There are already two people in this conversation.
 rooms_room_full_call_to_action_nonFx_label=Download {{brandShortname}} to start your own
 rooms_room_full_call_to_action_label=Learn more about {{clientShortname}} »
 rooms_room_joined_label=Someone has joined the conversation!
 rooms_room_join_label=Join the conversation
 rooms_display_name_guest=Guest
+rooms_unavailable_notification_message=Sorry, you cannot join this conversation. The link may be expired or invalid.
+rooms_media_denied_message=We could not get access to your microphone or camera. Please reload the page to try again.
 
 ## LOCALIZATION_NOTE(standalone_title_with_status): {{clientShortname}} will be
 ## replaced by the brand name and {{currentStatus}} will be replaced
 ## by the current call status (Connecting, Ringing, etc.)
 standalone_title_with_status={{clientShortname}} — {{currentStatus}}
 status_in_conversation=In conversation
 status_conversation_ended=Conversation ended
 status_error=Something went wrong
--- a/browser/components/loop/test/desktop-local/roomViews_test.js
+++ b/browser/components/loop/test/desktop-local/roomViews_test.js
@@ -60,16 +60,17 @@ describe("loop.roomViews", function () {
       var testView = TestUtils.renderIntoDocument(TestView({
         roomStore: roomStore
       }));
 
       expect(testView.state).eql({
         roomState: ROOM_STATES.INIT,
         audioMuted: false,
         videoMuted: false,
+        failureReason: undefined,
         foo: "bar"
       });
     });
 
     it("should listen to store changes", function() {
       var TestView = React.createClass({
         mixins: [loop.roomViews.ActiveRoomStoreMixin],
         render: function() { return React.DOM.div(); }
--- a/browser/components/loop/test/shared/activeRoomStore_test.js
+++ b/browser/components/loop/test/shared/activeRoomStore_test.js
@@ -1,17 +1,19 @@
 /* global chai, loop */
 
 var expect = chai.expect;
 var sharedActions = loop.shared.actions;
 
 describe("loop.store.ActiveRoomStore", function () {
   "use strict";
 
+  var SERVER_CODES = loop.store.SERVER_CODES;
   var ROOM_STATES = loop.store.ROOM_STATES;
+  var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
   var sandbox, dispatcher, store, fakeMozLoop, fakeSdkDriver;
   var fakeMultiplexGum;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
     sandbox.useFakeTimers();
 
     dispatcher = new loop.Dispatcher();
@@ -86,29 +88,50 @@ describe("loop.store.ActiveRoomStore", f
     it("should log the error", function() {
       store.roomFailure({error: fakeError});
 
       sinon.assert.calledOnce(console.error);
       sinon.assert.calledWith(console.error,
         sinon.match(ROOM_STATES.READY), fakeError);
     });
 
-    it("should set the state to `FULL` on server errno 202", function() {
-      fakeError.errno = 202;
+    it("should set the state to `FULL` on server error room full", function() {
+      fakeError.errno = SERVER_CODES.ROOM_FULL;
 
       store.roomFailure({error: fakeError});
 
       expect(store._storeState.roomState).eql(ROOM_STATES.FULL);
     });
 
     it("should set the state to `FAILED` on generic error", function() {
       store.roomFailure({error: fakeError});
 
       expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
+      expect(store._storeState.failureReason).eql(FAILURE_REASONS.UNKNOWN);
     });
+
+    it("should set the failureReason to EXPIRED_OR_INVALID on server error: " +
+      "invalid token", function() {
+        fakeError.errno = SERVER_CODES.INVALID_TOKEN;
+
+        store.roomFailure({error: fakeError});
+
+        expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
+        expect(store._storeState.failureReason).eql(FAILURE_REASONS.EXPIRED_OR_INVALID);
+      });
+
+    it("should set the failureReason to EXPIRED_OR_INVALID on server error: " +
+      "expired", function() {
+        fakeError.errno = SERVER_CODES.EXPIRED;
+
+        store.roomFailure({error: fakeError});
+
+        expect(store._storeState.roomState).eql(ROOM_STATES.FAILED);
+        expect(store._storeState.failureReason).eql(FAILURE_REASONS.EXPIRED_OR_INVALID);
+      });
   });
 
   describe("#setupWindowData", function() {
     var fakeToken, fakeRoomData;
 
     beforeEach(function() {
       fakeToken = "337-ff-54";
       fakeRoomData = {
@@ -239,16 +262,24 @@ describe("loop.store.ActiveRoomStore", f
     });
   });
 
   describe("#joinRoom", function() {
     beforeEach(function() {
       store.setStoreState({roomToken: "tokenFake"});
     });
 
+    it("should reset failureReason", function() {
+      store.setStoreState({failureReason: "Test"});
+
+      store.joinRoom();
+
+      expect(store.getStoreState().failureReason).eql(undefined);
+    });
+
     it("should call rooms.join on mozLoop", function() {
       store.joinRoom();
 
       sinon.assert.calledOnce(fakeMozLoop.rooms.join);
       sinon.assert.calledWith(fakeMozLoop.rooms.join, "tokenFake");
     });
 
     it("should dispatch `JoinedRoom` on success", function() {
@@ -375,55 +406,67 @@ describe("loop.store.ActiveRoomStore", f
     it("should set the state to `SESSION_CONNECTED`", function() {
       store.connectedToSdkServers(new sharedActions.ConnectedToSdkServers());
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.SESSION_CONNECTED);
     });
   });
 
   describe("#connectionFailure", function() {
+    var connectionFailureAction;
+
     beforeEach(function() {
       store.setStoreState({
         roomState: ROOM_STATES.JOINED,
         roomToken: "fakeToken",
         sessionToken: "1627384950"
       });
+
+      connectionFailureAction = new sharedActions.ConnectionFailure({
+        reason: "FAIL"
+      });
+    });
+
+    it("should store the failure reason", function() {
+      store.connectionFailure(connectionFailureAction);
+
+      expect(store.getStoreState().failureReason).eql("FAIL");
     });
 
     it("should reset the multiplexGum", function() {
-      store.leaveRoom();
+      store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(fakeMultiplexGum.reset);
     });
 
     it("should disconnect from the servers via the sdk", function() {
-      store.connectionFailure();
+      store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(fakeSdkDriver.disconnectSession);
     });
 
     it("should clear any existing timeout", function() {
       sandbox.stub(window, "clearTimeout");
       store._timeout = {};
 
-      store.connectionFailure();
+      store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(clearTimeout);
     });
 
     it("should call mozLoop.rooms.leave", function() {
-      store.connectionFailure();
+      store.connectionFailure(connectionFailureAction);
 
       sinon.assert.calledOnce(fakeMozLoop.rooms.leave);
       sinon.assert.calledWithExactly(fakeMozLoop.rooms.leave,
         "fakeToken", "1627384950");
     });
 
     it("should set the state to `FAILED`", function() {
-      store.connectionFailure();
+      store.connectionFailure(connectionFailureAction);
 
       expect(store.getStoreState().roomState).eql(ROOM_STATES.FAILED);
     });
   });
 
   describe("#setMute", function() {
     it("should save the mute state for the audio stream", function() {
       store.setStoreState({audioMuted: false});
--- a/browser/components/loop/test/shared/otSdkDriver_test.js
+++ b/browser/components/loop/test/shared/otSdkDriver_test.js
@@ -2,26 +2,29 @@
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 var expect = chai.expect;
 
 describe("loop.OTSdkDriver", function () {
   "use strict";
 
   var sharedActions = loop.shared.actions;
-
+  var FAILURE_REASONS = loop.shared.utils.FAILURE_REASONS;
   var sandbox;
   var dispatcher, driver, publisher, sdk, session, sessionData;
-  var fakeLocalElement, fakeRemoteElement, publisherConfig;
+  var fakeLocalElement, fakeRemoteElement, publisherConfig, fakeEvent;
 
   beforeEach(function() {
     sandbox = sinon.sandbox.create();
 
     fakeLocalElement = {fake: 1};
     fakeRemoteElement = {fake: 2};
+    fakeEvent = {
+      preventDefault: sinon.stub()
+    };
     publisherConfig = {
       fake: "config"
     };
     sessionData = {
       apiKey: "1234567890",
       sessionId: "3216549870",
       sessionToken: "1357924680"
     };
@@ -29,24 +32,24 @@ describe("loop.OTSdkDriver", function ()
     dispatcher = new loop.Dispatcher();
     session = _.extend({
       connect: sinon.stub(),
       disconnect: sinon.stub(),
       publish: sinon.stub(),
       subscribe: sinon.stub()
     }, Backbone.Events);
 
-    publisher = {
+    publisher = _.extend({
       destroy: sinon.stub(),
       publishAudio: sinon.stub(),
       publishVideo: sinon.stub()
-    };
+    }, Backbone.Events);
 
     sdk = {
-      initPublisher: sinon.stub(),
+      initPublisher: sinon.stub().returns(publisher),
       initSession: sinon.stub().returns(session)
     };
 
     driver = new loop.OTSdkDriver({
       dispatcher: dispatcher,
       sdk: sdk
     });
   });
@@ -75,61 +78,16 @@ describe("loop.OTSdkDriver", function ()
         getLocalElementFunc: function() {return fakeLocalElement;},
         getRemoteElementFunc: function() {return fakeRemoteElement;},
         publisherConfig: publisherConfig
       }));
 
       sinon.assert.calledOnce(sdk.initPublisher);
       sinon.assert.calledWith(sdk.initPublisher, fakeLocalElement, publisherConfig);
     });
-
-    describe("On Publisher Complete", function() {
-      it("should publish the stream if the connection is ready", function() {
-        sdk.initPublisher.callsArgWith(2, null);
-
-        driver.session = session;
-        driver._sessionConnected = true;
-
-        dispatcher.dispatch(new sharedActions.SetupStreamElements({
-          getLocalElementFunc: function() {return fakeLocalElement;},
-          getRemoteElementFunc: function() {return fakeRemoteElement;},
-          publisherConfig: publisherConfig
-        }));
-
-        sinon.assert.calledOnce(session.publish);
-      });
-
-      it("should dispatch connectionFailure if connecting failed", function() {
-        sdk.initPublisher.callsArgWith(2, new Error("Failure"));
-
-        // Special stub, as we want to use the dispatcher, but also know that
-        // we've been called correctly for the second dispatch.
-        var dispatchStub = (function() {
-          var originalDispatch = dispatcher.dispatch.bind(dispatcher);
-          return sandbox.stub(dispatcher, "dispatch", function(action) {
-            originalDispatch(action);
-          });
-        }());
-
-        driver.session = session;
-        driver._sessionConnected = true;
-
-        dispatcher.dispatch(new sharedActions.SetupStreamElements({
-          getLocalElementFunc: function() {return fakeLocalElement;},
-          getRemoteElementFunc: function() {return fakeRemoteElement;},
-          publisherConfig: publisherConfig
-        }));
-
-        sinon.assert.called(dispatcher.dispatch);
-        sinon.assert.calledWithMatch(dispatcher.dispatch,
-          sinon.match.hasOwn("name", "connectionFailure"));
-        sinon.assert.calledWithMatch(dispatcher.dispatch,
-          sinon.match.hasOwn("reason", "noMedia"));
-      });
-    });
   });
 
   describe("#setMute", function() {
     beforeEach(function() {
       sdk.initPublisher.returns(publisher);
 
       dispatcher.dispatch(new sharedActions.SetupStreamElements({
         getLocalElementFunc: function() {return fakeLocalElement;},
@@ -189,17 +147,17 @@ describe("loop.OTSdkDriver", function ()
         sandbox.stub(dispatcher, "dispatch");
 
         driver.connectSession(sessionData);
 
         sinon.assert.calledOnce(dispatcher.dispatch);
         sinon.assert.calledWithMatch(dispatcher.dispatch,
           sinon.match.hasOwn("name", "connectionFailure"));
         sinon.assert.calledWithMatch(dispatcher.dispatch,
-          sinon.match.hasOwn("reason", "couldNotConnect"));
+          sinon.match.hasOwn("reason", FAILURE_REASONS.COULD_NOT_CONNECT));
       });
     });
   });
 
   describe("#disconnectionSession", function() {
     it("should disconnect the session", function() {
       driver.session = session;
 
@@ -264,17 +222,17 @@ describe("loop.OTSdkDriver", function ()
           session.trigger("sessionDisconnected", {
             reason: "networkDisconnected"
           });
 
           sinon.assert.calledOnce(dispatcher.dispatch);
           sinon.assert.calledWithMatch(dispatcher.dispatch,
             sinon.match.hasOwn("name", "connectionFailure"));
           sinon.assert.calledWithMatch(dispatcher.dispatch,
-            sinon.match.hasOwn("reason", "networkDisconnected"));
+            sinon.match.hasOwn("reason", FAILURE_REASONS.NETWORK_DISCONNECTED));
         });
     });
 
     describe("streamCreated", function() {
       var fakeStream;
 
       beforeEach(function() {
         fakeStream = {
@@ -323,10 +281,46 @@ describe("loop.OTSdkDriver", function ()
         function() {
           session.trigger("connectionCreated", {
             connection: {id: "localUser"}
           });
 
           sinon.assert.notCalled(dispatcher.dispatch);
         });
     });
+
+    describe("accessAllowed", function() {
+      it("should publish the stream if the connection is ready", function() {
+        driver._sessionConnected = true;
+
+        publisher.trigger("accessAllowed", fakeEvent);
+
+        sinon.assert.calledOnce(session.publish);
+      });
+    });
+
+    describe("accessDenied", function() {
+      it("should prevent the default event behavior", function() {
+        publisher.trigger("accessDenied", fakeEvent);
+
+        sinon.assert.calledOnce(fakeEvent.preventDefault);
+      });
+
+      it("should dispatch connectionFailure", function() {
+        publisher.trigger("accessDenied", fakeEvent);
+
+        sinon.assert.called(dispatcher.dispatch);
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("name", "connectionFailure"));
+        sinon.assert.calledWithMatch(dispatcher.dispatch,
+          sinon.match.hasOwn("reason", FAILURE_REASONS.MEDIA_DENIED));
+      });
+    });
+
+    describe("accessDialogOpened", function() {
+      it("should prevent the default event behavior", function() {
+        publisher.trigger("accessDialogOpened", fakeEvent);
+
+        sinon.assert.calledOnce(fakeEvent.preventDefault);
+      });
+    });
   });
 });
--- a/browser/components/loop/test/standalone/standaloneRoomViews_test.js
+++ b/browser/components/loop/test/standalone/standaloneRoomViews_test.js
@@ -134,16 +134,26 @@ describe("loop.standaloneRoomViews", fun
           function() {
             activeRoomStore.setStoreState({roomState: ROOM_STATES.FULL});
 
             expect(view.getDOMNode().querySelector(".full-room-message"))
               .not.eql(null);
           });
       });
 
+      describe("Failed room message", function() {
+        it("should display a failed room message on FAILED",
+          function() {
+            activeRoomStore.setStoreState({roomState: ROOM_STATES.FAILED});
+
+            expect(view.getDOMNode().querySelector(".failed-room-message"))
+              .not.eql(null);
+          });
+      });
+
       describe("Join button", function() {
         function getJoinButton(view) {
           return view.getDOMNode().querySelector(".btn-join");
         }
 
         it("should render the Join button when room isn't active", function() {
           activeRoomStore.setStoreState({roomState: ROOM_STATES.READY});
 
--- a/browser/components/loop/ui/ui-showcase.js
+++ b/browser/components/loop/ui/ui-showcase.js
@@ -603,16 +603,26 @@
             Example({summary: "Standalone room conversation (full - non FFx user)"}, 
               React.DOM.div({className: "standalone"}, 
                 StandaloneRoomView({
                   dispatcher: dispatcher, 
                   activeRoomStore: activeRoomStore, 
                   roomState: ROOM_STATES.FULL, 
                   helper: {isFirefox: returnFalse}})
               )
+            ), 
+
+            Example({summary: "Standalone room conversation (failed)"}, 
+              React.DOM.div({className: "standalone"}, 
+                StandaloneRoomView({
+                  dispatcher: dispatcher, 
+                  activeRoomStore: activeRoomStore, 
+                  roomState: ROOM_STATES.FAILED, 
+                  helper: {isFirefox: returnFalse}})
+              )
             )
           ), 
 
           Section({name: "SVG icons preview"}, 
             Example({summary: "16x16"}, 
               SVGIcons(null)
             )
           )
--- a/browser/components/loop/ui/ui-showcase.jsx
+++ b/browser/components/loop/ui/ui-showcase.jsx
@@ -604,16 +604,26 @@
               <div className="standalone">
                 <StandaloneRoomView
                   dispatcher={dispatcher}
                   activeRoomStore={activeRoomStore}
                   roomState={ROOM_STATES.FULL}
                   helper={{isFirefox: returnFalse}} />
               </div>
             </Example>
+
+            <Example summary="Standalone room conversation (failed)">
+              <div className="standalone">
+                <StandaloneRoomView
+                  dispatcher={dispatcher}
+                  activeRoomStore={activeRoomStore}
+                  roomState={ROOM_STATES.FAILED}
+                  helper={{isFirefox: returnFalse}} />
+              </div>
+            </Example>
           </Section>
 
           <Section name="SVG icons preview">
             <Example summary="16x16">
               <SVGIcons />
             </Example>
           </Section>
 
--- a/browser/components/sessionstore/test/browser_586068-reload.js
+++ b/browser/components/sessionstore/test/browser_586068-reload.js
@@ -4,137 +4,51 @@
 
 const PREF_RESTORE_ON_DEMAND = "browser.sessionstore.restore_on_demand";
 
 function test() {
   TestRunner.run();
 }
 
 function runTests() {
-  // Request a longer timeout because the test takes quite a while
-  // to complete on slow Windows debug machines and we would otherwise
-  // see a lot of (not so) intermittent test failures.
-  requestLongerTimeout(2);
-
   Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, true);
   registerCleanupFunction(function () {
     Services.prefs.clearUserPref(PREF_RESTORE_ON_DEMAND);
   });
 
   let state = { windows: [{ tabs: [
     { entries: [{ url: "http://example.org/#1" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#2" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#3" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#4" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#5" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#6" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#7" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#8" }], extData: { "uniq": r() } },
     { entries: [{ url: "http://example.org/#9" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#10" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#11" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#12" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#13" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#14" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#15" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#16" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#17" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.org/#18" }], extData: { "uniq": r() } }
   ], selected: 1 }] };
 
   let loadCount = 0;
-  gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
-    loadCount++;
-    is(aBrowser.currentURI.spec, state.windows[0].tabs[loadCount - 1].entries[0].url,
-       "load " + loadCount + " - browser loaded correct url");
+  gBrowser.tabContainer.addEventListener("SSTabRestored", function onRestored(event) {
+    let tab = event.target;
+    let browser = tab.linkedBrowser;
+    let tabData = state.windows[0].tabs[loadCount++];
 
-    if (loadCount <= state.windows[0].tabs.length) {
-      // double check that this tab was the right one
-      let expectedData = state.windows[0].tabs[loadCount - 1].extData.uniq;
-      let tab;
-      for (let i = 0; i < window.gBrowser.tabs.length; i++) {
-        if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
-          tab = window.gBrowser.tabs[i];
-      }
-      is(ss.getTabValue(tab, "uniq"), expectedData,
-         "load " + loadCount + " - correct tab was restored");
+    // double check that this tab was the right one
+    is(browser.currentURI.spec, tabData.entries[0].url,
+       "load " + loadCount + " - browser loaded correct url");
+    is(ss.getTabValue(tab, "uniq"), tabData.extData.uniq,
+       "load " + loadCount + " - correct tab was restored");
 
-      if (loadCount == state.windows[0].tabs.length) {
-        gProgressListener.unsetCallback();
-        executeSoon(function () {
-          reloadAllTabs(state, function () {
-            waitForBrowserState(TestRunner.backupState, testCascade);
-          });
-        });
-      } else {
-        // reload the next tab
-        window.gBrowser.reloadTab(window.gBrowser.tabs[loadCount]);
-      }
+    if (loadCount == state.windows[0].tabs.length) {
+      gBrowser.tabContainer.removeEventListener("SSTabRestored", onRestored);
+
+      executeSoon(function () {
+        waitForBrowserState(TestRunner.backupState, finish);
+      });
+    } else {
+      // reload the next tab
+      gBrowser.browsers[loadCount].reload();
     }
   });
 
   yield ss.setBrowserState(JSON.stringify(state));
 }
-
-function testCascade() {
-  Services.prefs.setBoolPref(PREF_RESTORE_ON_DEMAND, false);
-
-  let state = { windows: [{ tabs: [
-    { entries: [{ url: "http://example.com/#1" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.com/#2" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.com/#3" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.com/#4" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.com/#5" }], extData: { "uniq": r() } },
-    { entries: [{ url: "http://example.com/#6" }], extData: { "uniq": r() } }
-  ] }] };
-
-  let loadCount = 0;
-  gProgressListener.setCallback(function (aBrowser, aNeedRestore, aRestoring, aRestored) {
-    if (++loadCount < state.windows[0].tabs.length) {
-      return;
-    }
-
-    gProgressListener.unsetCallback();
-    executeSoon(function () {
-      reloadAllTabs(state, next);
-    });
-  });
-
-  ss.setBrowserState(JSON.stringify(state));
-}
-
-function reloadAllTabs(aState, aCallback) {
-  // Simulate a left mouse button click with no modifiers, which is what
-  // Command-R, or clicking reload does.
-  let fakeEvent = {
-    button: 0,
-    metaKey: false,
-    altKey: false,
-    ctrlKey: false,
-    shiftKey: false
-  };
-
-  let loadCount = 0;
-  gWebProgressListener.setCallback(function (aBrowser) {
-    if (++loadCount <= aState.windows[0].tabs.length) {
-      // double check that this tab was the right one
-      let expectedData = aState.windows[0].tabs[loadCount - 1].extData.uniq;
-      let tab;
-      for (let i = 0; i < window.gBrowser.tabs.length; i++) {
-        if (!tab && window.gBrowser.tabs[i].linkedBrowser == aBrowser)
-          tab = window.gBrowser.tabs[i];
-      }
-      is(ss.getTabValue(tab, "uniq"), expectedData,
-         "load " + loadCount + " - correct tab was reloaded");
-
-      if (loadCount == aState.windows[0].tabs.length) {
-        gWebProgressListener.unsetCallback();
-        executeSoon(aCallback);
-      } else {
-        // reload the next tab
-        window.gBrowser.selectTabAtIndex(loadCount);
-        BrowserReloadOrDuplicate(fakeEvent);
-      }
-    }
-  });
-
-  BrowserReloadOrDuplicate(fakeEvent);
-}
--- a/browser/experiments/Makefile.in
+++ b/browser/experiments/Makefile.in
@@ -3,15 +3,14 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 include $(topsrcdir)/config/rules.mk
 
 # This is so hacky. Waiting on bug 988938.
 addondir = $(srcdir)/test/addons
 testdir = $(abspath $(DEPTH)/_tests/xpcshell/browser/experiments/test/xpcshell)
 
-libs::
+misc:: $(call mkdir_deps,$(testdir))
 	$(EXIT_ON_ERROR) \
-	$(NSINSTALL) -D $(testdir); \
 	for dir in $(addondir)/*; do \
 	  base=`basename $$dir`; \
 	  (cd $$dir && zip -qr $(testdir)/$$base.xpi *); \
 	done
--- a/browser/experiments/moz.build
+++ b/browser/experiments/moz.build
@@ -1,12 +1,14 @@
 # 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/.
 
+HAS_MISC_RULE = True
+
 EXTRA_COMPONENTS += [
     'Experiments.manifest',
     'ExperimentsService.js',
 ]
 
 EXTRA_JS_MODULES.experiments += [
   'Experiments.jsm',
 ]
--- a/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.properties
+++ b/browser/locales/en-US/chrome/browser/aboutPrivateBrowsing.properties
@@ -1,2 +1,6 @@
-title=You're browsing privately
-title.normal=Open a private window?
\ No newline at end of file
+# 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/.
+
+title=You're browsing privately
+title.normal=Open a private window?
--- a/build/mobile/robocop/FennecNativeActions.java
+++ b/build/mobile/robocop/FennecNativeActions.java
@@ -36,17 +36,17 @@ public class FennecNativeActions impleme
         mSolo = robocop;
         mInstr = instrumentation;
         mAsserter = asserter;
 
         GeckoLoader.loadSQLiteLibs(activity, activity.getApplication().getPackageResourcePath());
     }
 
     class GeckoEventExpecter implements RepeatedEventExpecter {
-        private static final int MAX_WAIT_MS = 90000;
+        private static final int MAX_WAIT_MS = 180000;
 
         private volatile boolean mIsRegistered;
 
         private final String mGeckoEvent;
         private final GeckoEventListener mListener;
 
         private volatile boolean mEventEverReceived;
         private String mEventData;
--- a/dom/system/gonk/AutoMounter.cpp
+++ b/dom/system/gonk/AutoMounter.cpp
@@ -73,16 +73,17 @@ USING_MTP_NAMESPACE
 #define GB_SYS_UMS_ENABLE     "/sys/devices/virtual/usb_composite/usb_mass_storage/enable"
 #define GB_SYS_USB_CONFIGURED "/sys/devices/virtual/switch/usb_configuration/state"
 
 #define ICS_SYS_USB_FUNCTIONS "/sys/devices/virtual/android_usb/android0/functions"
 #define ICS_SYS_UMS_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mass_storage"
 #define ICS_SYS_MTP_DIRECTORY "/sys/devices/virtual/android_usb/android0/f_mtp"
 #define ICS_SYS_USB_STATE     "/sys/devices/virtual/android_usb/android0/state"
 
+#undef USE_DEBUG    // MozMtpDatabase.h also defines USE_DEBUG
 #define USE_DEBUG 0
 
 #undef LOG
 #undef LOGW
 #undef ERR
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO,  "AutoMounter", ## args)
 #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN,  "AutoMounter", ## args)
 #define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "AutoMounter", ## args)
@@ -710,17 +711,25 @@ AutoMounter::UpdateState()
     char functionsStr[60];
     if (!ReadSysFile(ICS_SYS_USB_FUNCTIONS, functionsStr, sizeof(functionsStr))) {
       ERR("Error reading file '%s': %s", ICS_SYS_USB_FUNCTIONS, strerror(errno));
       functionsStr[0] = '\0';
     }
     DBG("UpdateState: USB functions: '%s'", functionsStr);
 
     bool  usbConfigured = IsUsbConfigured();
-    umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
+
+    // On the Nexus 4/5, it advertises that the UMS usb function is available,
+    // but we have a further requirement that mass_storage be in the
+    // persist.sys.usb.config property.
+    char persistSysUsbConfig[PROPERTY_VALUE_MAX];
+    property_get(PERSIST_SYS_USB_CONFIG, persistSysUsbConfig, "");
+    if (IsUsbFunctionEnabled(persistSysUsbConfig, USB_FUNC_UMS)) {
+      umsAvail = (access(ICS_SYS_UMS_DIRECTORY, F_OK) == 0);
+    }
     if (umsAvail) {
       umsConfigured = usbConfigured && strstr(functionsStr, USB_FUNC_UMS) != nullptr;
       umsEnabled = (mMode == AUTOMOUNTER_ENABLE_UMS) ||
                    ((mMode == AUTOMOUNTER_DISABLE_WHEN_UNPLUGGED) && umsConfigured);
     } else {
       umsConfigured = false;
       umsEnabled = false;
     }
@@ -1225,78 +1234,19 @@ public:
         FROM_HERE,
         NewRunnableFunction(UsbCableEventIOThread));
   }
 };
 
 static StaticRefPtr<UsbCableObserver> sUsbCableObserver;
 static StaticRefPtr<AutoMounterSetting> sAutoMounterSetting;
 
-static void
-InitVolumeConfig()
-{
-  // This function uses /system/etc/volume.cfg to add additional volumes
-  // to the Volume Manager.
-  //
-  // This is useful on devices like the Nexus 4, which have no physical sd card
-  // or dedicated partition.
-  //
-  // The format of the volume.cfg file is as follows:
-  // create volume-name mount-point
-  // Blank lines and lines starting with the hash character "#" will be ignored.
-
-  nsCOMPtr<nsIVolumeService> vs = do_GetService(NS_VOLUMESERVICE_CONTRACTID);
-  NS_ENSURE_TRUE_VOID(vs);
-
-  ScopedCloseFile fp;
-  int n = 0;
-  char line[255];
-  char *command, *vol_name_cstr, *mount_point_cstr, *save_ptr;
-  const char *filename = "/system/etc/volume.cfg";
-  if (!(fp = fopen(filename, "r"))) {
-    LOG("Unable to open volume configuration file '%s' - ignoring", filename);
-    return;
-  }
-  while(fgets(line, sizeof(line), fp)) {
-    const char *delim = " \t\n";
-    n++;
-
-    if (line[0] == '#')
-      continue;
-    if (!(command = strtok_r(line, delim, &save_ptr))) {
-      // Blank line - ignore
-      continue;
-    }
-    if (!strcmp(command, "create")) {
-      if (!(vol_name_cstr = strtok_r(nullptr, delim, &save_ptr))) {
-        ERR("No vol_name in %s line %d",  filename, n);
-        continue;
-      }
-      if (!(mount_point_cstr = strtok_r(nullptr, delim, &save_ptr))) {
-        ERR("No mount point for volume '%s'. %s line %d", vol_name_cstr, filename, n);
-        continue;
-      }
-      nsString  mount_point = NS_ConvertUTF8toUTF16(mount_point_cstr);
-      nsString vol_name = NS_ConvertUTF8toUTF16(vol_name_cstr);
-      nsresult rv;
-      rv = vs->CreateFakeVolume(vol_name, mount_point);
-      NS_ENSURE_SUCCESS_VOID(rv);
-      rv = vs->SetFakeVolumeState(vol_name, nsIVolume::STATE_MOUNTED);
-      NS_ENSURE_SUCCESS_VOID(rv);
-    }
-    else {
-      ERR("Unrecognized command: '%s'", command);
-    }
-  }
-}
-
 void
 InitAutoMounter()
 {
-  InitVolumeConfig();
   InitVolumeManager();
   sAutoMounterSetting = new AutoMounterSetting();
 
   XRE_GetIOMessageLoop()->PostTask(
       FROM_HERE,
       NewRunnableFunction(InitAutoMounterIOThread));
 
   // Switch Observers need to run on the main thread, so we need to
--- a/dom/system/gonk/MozMtpDatabase.cpp
+++ b/dom/system/gonk/MozMtpDatabase.cpp
@@ -540,16 +540,19 @@ MozMtpDatabase::FindStorageIDFor(const n
 void
 MozMtpDatabase::AddStorage(MtpStorageID aStorageID,
                            const char* aPath,
                            const char* aName)
 {
   // This is called on the IOThread from MozMtpStorage::StorageAvailable
   MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
 
+  MTP_DBG("StorageID: 0x%08x aPath: '%s' aName: '%s'",
+          aStorageID, aPath, aName);
+
   PRFileInfo  fileInfo;
   if (PR_GetFileInfo(aPath, &fileInfo) != PR_SUCCESS) {
     MTP_ERR("'%s' doesn't exist", aPath);
     return;
   }
   if (fileInfo.type != PR_FILE_DIRECTORY) {
     MTP_ERR("'%s' isn't a directory", aPath);
     return;
--- a/dom/system/gonk/MozMtpStorage.cpp
+++ b/dom/system/gonk/MozMtpStorage.cpp
@@ -58,24 +58,28 @@ MozMtpStorage::Notify(Volume* const& aVo
     return;
   }
   Volume::STATE volState = aVolume->State();
 
   MTP_LOG("Volume %s mStorageID 0x%08x state changed to %s SharingEnabled: %d",
           aVolume->NameStr(), mStorageID, aVolume->StateStr(),
           aVolume->IsSharingEnabled());
 
+  // vol->IsSharingEnabled really only applies to UMS volumes. We assume that
+  // that as long as MTP is enabled, then all volumes will be shared. The UI
+  // currently doesn't give us anything more granular than on/off.
+
   if (mMtpStorage) {
-    if (volState != nsIVolume::STATE_MOUNTED || !aVolume->IsSharingEnabled()) {
+    if (volState != nsIVolume::STATE_MOUNTED) {
       // The volume is no longer accessible. We need to remove this storage
       // from the MTP server
       StorageUnavailable();
     }
   } else {
-    if (volState == nsIVolume::STATE_MOUNTED && aVolume->IsSharingEnabled()) {
+    if (volState == nsIVolume::STATE_MOUNTED) {
       // The volume is accessible. Tell the MTP server.
       StorageAvailable();
     }
   }
 }
 
 void
 MozMtpStorage::StorageAvailable()
--- a/dom/system/gonk/OpenFileFinder.cpp
+++ b/dom/system/gonk/OpenFileFinder.cpp
@@ -5,16 +5,17 @@
 #include "OpenFileFinder.h"
 
 #include "mozilla/FileUtils.h"
 #include "nsPrintfCString.h"
 
 #include <sys/stat.h>
 #include <errno.h>
 
+#undef USE_DEBUG
 #define USE_DEBUG 0
 
 #undef LOG
 #undef LOGW
 #undef ERR
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO,  "OpenFileFinder", ## args)
 #define LOGW(args...) __android_log_print(ANDROID_LOG_WARN,  "OpenFileFinder", ## args)
 #define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "OpenFileFinder", ## args)
--- a/dom/system/gonk/TimeZoneSettingObserver.cpp
+++ b/dom/system/gonk/TimeZoneSettingObserver.cpp
@@ -21,16 +21,17 @@
 #include "TimeZoneSettingObserver.h"
 #include "xpcpublic.h"
 #include "nsContentUtils.h"
 #include "nsPrintfCString.h"
 #include "mozilla/dom/BindingUtils.h"
 #include "mozilla/dom/SettingChangeNotificationBinding.h"
 
 #undef LOG
+#undef ERR
 #define LOG(args...)  __android_log_print(ANDROID_LOG_INFO, "Time Zone Setting" , ## args)
 #define ERR(args...)  __android_log_print(ANDROID_LOG_ERROR, "Time Zone Setting" , ## args)
 
 #define TIME_TIMEZONE       "time.timezone"
 #define MOZSETTINGS_CHANGED "mozsettings-changed"
 
 using namespace mozilla;
 using namespace mozilla::dom;
--- a/dom/system/gonk/Volume.cpp
+++ b/dom/system/gonk/Volume.cpp
@@ -82,16 +82,48 @@ Volume::Volume(const nsCSubstring& aName
     mIsFormatting(false),
     mIsUnmounting(false),
     mId(sNextId++)
 {
   DBG("Volume %s: created", NameStr());
 }
 
 void
+Volume::Dump(const char* aLabel) const
+{
+  LOG("%s: Volume: %s (%d) is %s and %s @ %s gen %d locked %d",
+      aLabel,
+      NameStr(),
+      Id(),
+      StateStr(),
+      MediaPresent() ? "inserted" : "missing",
+      MountPoint().get(),
+      MountGeneration(),
+      (int)IsMountLocked());
+  LOG("%s:   Sharing %s Mounting %s Formating %s Unmounting %s",
+      aLabel,
+      CanBeShared() ? (IsSharingEnabled() ? (IsSharing() ? "en-y" : "en-n")
+                                          : "dis")
+                    : "x",
+      IsMountRequested() ? "req" : "n",
+      IsFormatRequested() ? (IsFormatting() ? "req-y" : "req-n")
+                          : (IsFormatting() ? "y" : "n"),
+      IsUnmountRequested() ? (IsUnmounting() ? "req-y" : "req-n")
+                           : (IsUnmounting() ? "y" : "n"));
+}
+
+void Volume::SetFakeVolume(const nsACString& aMountPoint)
+{
+  this->mMountLocked = false;
+  this->mCanBeShared = false;
+  this->mMountPoint = aMountPoint;
+  SetState(nsIVolume::STATE_MOUNTED);
+}
+
+void
 Volume::SetIsSharing(bool aIsSharing)
 {
   if (aIsSharing == mIsSharing) {
     return;
   }
   mIsSharing = aIsSharing;
   LOG("Volume %s: IsSharing set to %d state %s",
       NameStr(), (int)mIsSharing, StateStr(mState));
--- a/dom/system/gonk/Volume.h
+++ b/dom/system/gonk/Volume.h
@@ -49,16 +49,18 @@ public:
 
   static const char* StateStr(STATE aState) { return NS_VolumeStateStr(aState); }
   const char* StateStr() const  { return StateStr(mState); }
   STATE State() const           { return mState; }
 
   const nsCString& Name() const { return mName; }
   const char* NameStr() const   { return mName.get(); }
 
+  void Dump(const char* aLabel) const;
+
   // The mount point is the name of the directory where the volume is mounted.
   // (i.e. path that leads to the files stored on the volume).
   const nsCString& MountPoint() const { return mMountPoint; }
 
   uint32_t Id() const                 { return mId; }
 
   int32_t MountGeneration() const     { return mMountGeneration; }
   bool IsMountLocked() const          { return mMountLocked; }
@@ -69,16 +71,18 @@ public:
   bool IsSharingEnabled() const       { return mCanBeShared && mSharingEnabled; }
   bool IsFormatRequested() const      { return CanBeFormatted() && mFormatRequested; }
   bool IsMountRequested() const       { return CanBeMounted() && mMountRequested; }
   bool IsUnmountRequested() const     { return CanBeMounted() && mUnmountRequested; }
   bool IsSharing() const              { return mIsSharing; }
   bool IsFormatting() const           { return mIsFormatting; }
   bool IsUnmounting() const           { return mIsUnmounting; }
 
+  void SetFakeVolume(const nsACString& aMountPoint);
+
   void SetSharingEnabled(bool aSharingEnabled);
   void SetFormatRequested(bool aFormatRequested);
   void SetMountRequested(bool aMountRequested);
   void SetUnmountRequested(bool aUnmountRequested);
 
   typedef mozilla::Observer<Volume *>     EventObserver;
 
   // NOTE: that observers must live in the IOThread.
--- a/dom/system/gonk/VolumeManager.cpp
+++ b/dom/system/gonk/VolumeManager.cpp
@@ -39,16 +39,33 @@ VolumeManager::VolumeManager()
   DBG("VolumeManager constructor called");
 }
 
 VolumeManager::~VolumeManager()
 {
 }
 
 //static
+void
+VolumeManager::Dump(const char* aLabel)
+{
+  if (!sVolumeManager) {
+    LOG("%s: sVolumeManager == null", aLabel);
+    return;
+  }
+
+  VolumeArray::size_type  numVolumes = NumVolumes();
+  VolumeArray::index_type volIndex;
+  for (volIndex = 0; volIndex < numVolumes; volIndex++) {
+    RefPtr<Volume> vol = GetVolume(volIndex);
+    vol->Dump(aLabel);
+  }
+}
+
+//static
 size_t
 VolumeManager::NumVolumes()
 {
   if (!sVolumeManager) {
     return 0;
   }
   return sVolumeManager->mVolumeArray.Length();
 }
@@ -133,16 +150,71 @@ VolumeManager::FindAddVolumeByName(const
     return vol;
   }
   // No volume found, create and add a new one.
   vol = new Volume(aName);
   sVolumeManager->mVolumeArray.AppendElement(vol);
   return vol;
 }
 
+//static
+void VolumeManager::InitConfig()
+{
+  MOZ_ASSERT(MessageLoop::current() == XRE_GetIOMessageLoop());
+
+  // This function uses /system/etc/volume.cfg to add additional volumes
+  // to the Volume Manager.
+  //
+  // This is useful on devices like the Nexus 4, which have no physical sd card
+  // or dedicated partition.
+  //
+  // The format of the volume.cfg file is as follows:
+  // create volume-name mount-point
+  // Blank lines and lines starting with the hash character "#" will be ignored.
+
+  ScopedCloseFile fp;
+  int n = 0;
+  char line[255];
+  char *command, *volNamePtr, *mountPointPtr, *save_ptr;
+  const char *filename = "/system/etc/volume.cfg";
+  if (!(fp = fopen(filename, "r"))) {
+    LOG("Unable to open volume configuration file '%s' - ignoring", filename);
+    return;
+  }
+  while(fgets(line, sizeof(line), fp)) {
+    const char *delim = " \t\n";
+    n++;
+
+    if (line[0] == '#')
+      continue;
+    if (!(command = strtok_r(line, delim, &save_ptr))) {
+      // Blank line - ignore
+      continue;
+    }
+    if (!strcmp(command, "create")) {
+      if (!(volNamePtr = strtok_r(nullptr, delim, &save_ptr))) {
+        ERR("No vol_name in %s line %d",  filename, n);
+        continue;
+      }
+      if (!(mountPointPtr = strtok_r(nullptr, delim, &save_ptr))) {
+        ERR("No mount point for volume '%s'. %s line %d", volNamePtr, filename, n);
+        continue;
+      }
+      nsCString mountPoint(mountPointPtr);
+      nsCString volName(volNamePtr);
+
+      RefPtr<Volume> vol = FindAddVolumeByName(volName);
+      vol->SetFakeVolume(mountPoint);
+    }
+    else {
+      ERR("Unrecognized command: '%s'", command);
+    }
+  }
+}
+
 class VolumeListCallback : public VolumeResponseCallback
 {
   virtual void ResponseReceived(const VolumeCommand* aCommand)
   {
     switch (ResponseCode()) {
       case ::ResponseCode::VolumeListResult: {
         // Each line will look something like:
         //
@@ -153,18 +225,21 @@ class VolumeListCallback : public Volume
         nsCWhitespaceTokenizer tokenizer(ResponseStr());
         nsDependentCSubstring volName(tokenizer.nextToken());
         RefPtr<Volume> vol = VolumeManager::FindAddVolumeByName(volName);
         vol->HandleVoldResponse(ResponseCode(), tokenizer);
         break;
       }
 
       case ::ResponseCode::CommandOkay: {
-        // We've received the list of volumes. Tell anybody who
-        // is listening that we're open for business.
+        // We've received the list of volumes. Now read the Volume.cfg
+        // file to perform customizations, and then tell everybody
+        // that we're ready for business.
+        VolumeManager::InitConfig();
+        VolumeManager::Dump("READY");
         VolumeManager::SetState(VolumeManager::VOLUMES_READY);
         break;
       }
     }
   }
 };
 
 bool
--- a/dom/system/gonk/VolumeManager.h
+++ b/dom/system/gonk/VolumeManager.h
@@ -117,21 +117,23 @@ public:
   typedef mozilla::ObserverList<StateChangedEvent>  StateObserverList;
 
   static void RegisterStateObserver(StateObserver* aObserver);
   static void UnregisterStateObserver(StateObserver* aObserver);
 
   //-----------------------------------------------------------------------
 
   static void Start();
+  static void Dump(const char* aLabel);
 
   static VolumeArray::size_type NumVolumes();
   static TemporaryRef<Volume> GetVolume(VolumeArray::index_type aIndex);
   static TemporaryRef<Volume> FindVolumeByName(const nsCSubstring& aName);
   static TemporaryRef<Volume> FindAddVolumeByName(const nsCSubstring& aName);
+  static void InitConfig();
 
   static void       PostCommand(VolumeCommand* aCommand);
 
 protected:
 
   virtual void OnLineRead(int aFd, nsDependentCSubstring& aMessage);
   virtual void OnFileCanWriteWithoutBlocking(int aFd);
   virtual void OnError();
--- a/dom/system/gonk/VolumeServiceTest.cpp
+++ b/dom/system/gonk/VolumeServiceTest.cpp
@@ -12,16 +12,17 @@
 #include "nsThreadUtils.h"
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
 #include "nsIVolumeStat.h"
 #include "nsXULAppAPI.h"
 
 #include "mozilla/Services.h"
 
+#undef VOLUME_MANAGER_LOG_TAG
 #define VOLUME_MANAGER_LOG_TAG  "VolumeServiceTest"
 #include "VolumeManagerLog.h"
 
 using namespace mozilla::services;
 
 namespace mozilla {
 namespace system {
 
--- a/dom/system/gonk/nsVolume.cpp
+++ b/dom/system/gonk/nsVolume.cpp
@@ -49,17 +49,17 @@ NS_VolumeStateStr(int32_t aState)
 NS_IMPL_ISUPPORTS(nsVolume, nsIVolume)
 
 nsVolume::nsVolume(const Volume* aVolume)
   : mName(NS_ConvertUTF8toUTF16(aVolume->Name())),
     mMountPoint(NS_ConvertUTF8toUTF16(aVolume->MountPoint())),
     mState(aVolume->State()),
     mMountGeneration(aVolume->MountGeneration()),
     mMountLocked(aVolume->IsMountLocked()),
-    mIsFake(false),
+    mIsFake(!aVolume->CanBeShared()),
     mIsMediaPresent(aVolume->MediaPresent()),
     mIsSharing(aVolume->IsSharing()),
     mIsFormatting(aVolume->IsFormatting()),
     mIsUnmounting(aVolume->IsUnmounting())
 {
 }
 
 bool nsVolume::Equals(nsIVolume* aVolume)
--- a/dom/system/gonk/nsVolumeMountLock.cpp
+++ b/dom/system/gonk/nsVolumeMountLock.cpp
@@ -9,16 +9,17 @@
 
 #include "nsIObserverService.h"
 #include "nsIPowerManagerService.h"
 #include "nsIVolume.h"
 #include "nsIVolumeService.h"
 #include "nsString.h"
 #include "nsXULAppAPI.h"
 
+#undef VOLUME_MANAGER_LOG_TAG
 #define VOLUME_MANAGER_LOG_TAG  "nsVolumeMountLock"
 #include "VolumeManagerLog.h"
 #include "nsServiceManagerUtils.h"
 #include "mozilla/dom/power/PowerManagerService.h"
 
 using namespace mozilla::dom;
 using namespace mozilla::services;
 
--- a/dom/system/gonk/nsVolumeStat.cpp
+++ b/dom/system/gonk/nsVolumeStat.cpp
@@ -12,20 +12,16 @@ NS_IMPL_ISUPPORTS(nsVolumeStat, nsIVolum
 
 nsVolumeStat::nsVolumeStat(const nsAString& aPath)
 {
   if (statfs(NS_ConvertUTF16toUTF8(aPath).get(), &mStat) != 0) {
     memset(&mStat, 0, sizeof(mStat));
   }
 }
 
-nsVolumeStat::~nsVolumeStat()
-{
-}
-
 /* readonly attribute long long totalBytes; */
 NS_IMETHODIMP nsVolumeStat::GetTotalBytes(int64_t* aTotalBytes)
 {
   *aTotalBytes = mStat.f_blocks * mStat.f_bsize;
   return NS_OK;
 }
 
 /* readonly attribute long long freeBytes; */
--- a/dom/system/gonk/nsVolumeStat.h
+++ b/dom/system/gonk/nsVolumeStat.h
@@ -16,17 +16,15 @@ class nsVolumeStat : public nsIVolumeSta
 {
 public:
   NS_DECL_ISUPPORTS
   NS_DECL_NSIVOLUMESTAT
 
   nsVolumeStat(const nsAString& aPath);
 
 private:
-  ~nsVolumeStat();
-
   struct statfs mStat;
 };
 
 } // system
 } // mozilla
 
 #endif  // mozilla_system_nsvolumestat_h__
new file mode 100644
--- /dev/null
+++ b/dom/telephony/gonk/TelephonyAudioService.js
@@ -0,0 +1,151 @@
+/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*- */
+/* 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/. */
+
+"use strict";
+
+const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
+
+Cu.import("resource://gre/modules/Services.jsm");
+Cu.import("resource://gre/modules/XPCOMUtils.jsm");
+
+const nsIAudioManager = Ci.nsIAudioManager;
+const nsITelephonyAudioService = Ci.nsITelephonyAudioService;
+
+const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
+const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
+
+const AUDIO_STATE_NAME = [
+  "PHONE_STATE_NORMAL",
+  "PHONE_STATE_RINGTONE",
+  "PHONE_STATE_IN_CALL"
+];
+
+let DEBUG;
+function debug(s) {
+  dump("TelephonyAudioService: " + s + "\n");
+}
+
+XPCOMUtils.defineLazyGetter(this, "RIL", function () {
+  let obj = {};
+  Cu.import("resource://gre/modules/ril_consts.js", obj);
+  return obj;
+});
+
+XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
+  try {
+    return Cc["@mozilla.org/telephony/audiomanager;1"]
+             .getService(nsIAudioManager);
+  } catch (ex) {
+    //TODO on the phone this should not fall back as silently.
+
+    // Fake nsIAudioManager implementation so that we can run the telephony
+    // code in a non-Gonk build.
+    if (DEBUG) debug("Using fake audio manager.");
+    return {
+      microphoneMuted: false,
+      masterVolume: 1.0,
+      masterMuted: false,
+      phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
+      _forceForUse: {},
+
+      setForceForUse: function(usage, force) {
+        this._forceForUse[usage] = force;
+      },
+
+      getForceForUse: function(usage) {
+        return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
+      }
+    };
+  }
+});
+
+function TelephonyAudioService() {
+  this._updateDebugFlag();
+  Services.prefs.addObserver(kPrefRilDebuggingEnabled, this, false);
+}
+TelephonyAudioService.prototype = {
+  classDescription: "TelephonyAudioService",
+  classID: Components.ID("{c8605499-cfec-4cb5-a082-5f1f56d42adf}"),
+  contractID: "@mozilla.org/telephony/audio-service;1",
+  QueryInterface: XPCOMUtils.generateQI([Ci.nsITelephonyAudioService,
+                                         Ci.nsIObserver]),
+
+  _updateDebugFlag: function() {
+    try {
+      DEBUG = RIL.DEBUG_RIL ||
+              Services.prefs.getBoolPref(kPrefRilDebuggingEnabled);
+    } catch (e) {}
+  },
+
+  /**
+   * nsITelephonyAudioService interface.
+   */
+
+  get microphoneMuted() {
+    return gAudioManager.microphoneMuted;
+  },
+
+  set microphoneMuted(aMuted) {
+    if (aMuted == this.microphoneMuted) {
+      return;
+    }
+    gAudioManager.microphoneMuted = aMuted;
+  },
+
+  get speakerEnabled() {
+    let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
+    return (force == nsIAudioManager.FORCE_SPEAKER);
+  },
+
+  set speakerEnabled(aEnabled) {
+    if (aEnabled == this.speakerEnabled) {
+      return;
+    }
+    let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
+                           nsIAudioManager.FORCE_NONE;
+    gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
+  },
+
+  setPhoneState: function(aState) {
+    switch (aState) {
+      case nsITelephonyAudioService.PHONE_STATE_NORMAL:
+        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+        break;
+      case nsITelephonyAudioService.PHONE_STATE_RINGTONE:
+        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
+        break;
+      case nsITelephonyAudioService.PHONE_STATE_IN_CALL:
+        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
+        if (this.speakerEnabled) {
+          gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
+                                       nsIAudioManager.FORCE_SPEAKER);
+        }
+        break;
+      default:
+        throw new Error("Unknown audioPhoneState: " + aState);
+    }
+
+    if (DEBUG) {
+      debug("Put audio system into " + AUDIO_STATE_NAME[aState] + ": " +
+            aState + ", result is: " + gAudioManager.phoneState);
+    }
+  },
+
+  /**
+   * nsIObserver interface.
+   */
+
+  observe: function(aSubject, aTopic, aData) {
+    switch (aTopic) {
+      case NS_PREFBRANCH_PREFCHANGE_TOPIC_ID:
+        if (aData === kPrefRilDebuggingEnabled) {
+          this._updateDebugFlag();
+        }
+        break;
+    }
+  }
+};
+
+this.NSGetFactory = XPCOMUtils.generateNSGetFactory([TelephonyAudioService]);
new file mode 100644
--- /dev/null
+++ b/dom/telephony/gonk/TelephonyAudioService.manifest
@@ -0,0 +1,6 @@
+# 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/.
+
+component {c8605499-cfec-4cb5-a082-5f1f56d42adf} TelephonyAudioService.js
+contract @mozilla.org/telephony/audio-service;1 {c8605499-cfec-4cb5-a082-5f1f56d42adf}
--- a/dom/telephony/gonk/TelephonyService.js
+++ b/dom/telephony/gonk/TelephonyService.js
@@ -28,34 +28,28 @@ const MOBILECALLFORWARDINGOPTIONS_CID =
 const NS_XPCOM_SHUTDOWN_OBSERVER_ID = "xpcom-shutdown";
 
 const NS_PREFBRANCH_PREFCHANGE_TOPIC_ID = "nsPref:changed";
 
 const kPrefRilNumRadioInterfaces = "ril.numRadioInterfaces";
 const kPrefRilDebuggingEnabled = "ril.debugging.enabled";
 const kPrefDefaultServiceId = "dom.telephony.defaultServiceId";
 
-const nsIAudioManager = Ci.nsIAudioManager;
+const nsITelephonyAudioService = Ci.nsITelephonyAudioService;
 const nsITelephonyService = Ci.nsITelephonyService;
 
 const CALL_WAKELOCK_TIMEOUT = 5000;
 
 // Index of the CDMA second call which isn't held in RIL but only in TelephoyService.
 const CDMA_SECOND_CALL_INDEX = 2;
 
 const DIAL_ERROR_INVALID_STATE_ERROR = "InvalidStateError";
 const DIAL_ERROR_OTHER_CONNECTION_IN_USE = "OtherConnectionInUse";
 const DIAL_ERROR_BAD_NUMBER = RIL.GECKO_CALL_ERROR_BAD_NUMBER;
 
-const AUDIO_STATE_NAME = [
-  "PHONE_STATE_NORMAL",
-  "PHONE_STATE_RINGTONE",
-  "PHONE_STATE_IN_CALL"
-];
-
 const DEFAULT_EMERGENCY_NUMBERS = ["112", "911"];
 
 // MMI match groups
 const MMI_MATCH_GROUP_FULL_MMI = 1;
 const MMI_MATCH_GROUP_PROCEDURE = 2;
 const MMI_MATCH_GROUP_SERVICE_CODE = 3;
 const MMI_MATCH_GROUP_SIA = 4;
 const MMI_MATCH_GROUP_SIB = 5;
@@ -63,56 +57,32 @@ const MMI_MATCH_GROUP_SIC = 6;
 const MMI_MATCH_GROUP_PWD_CONFIRM = 7;
 const MMI_MATCH_GROUP_DIALING_NUMBER = 8;
 
 let DEBUG;
 function debug(s) {
   dump("TelephonyService: " + s + "\n");
 }
 
-XPCOMUtils.defineLazyGetter(this, "gAudioManager", function getAudioManager() {
-  try {
-    return Cc["@mozilla.org/telephony/audiomanager;1"]
-             .getService(nsIAudioManager);
-  } catch (ex) {
-    //TODO on the phone this should not fall back as silently.
-
-    // Fake nsIAudioManager implementation so that we can run the telephony
-    // code in a non-Gonk build.
-    if (DEBUG) debug("Using fake audio manager.");
-    return {
-      microphoneMuted: false,
-      masterVolume: 1.0,
-      masterMuted: false,
-      phoneState: nsIAudioManager.PHONE_STATE_CURRENT,
-      _forceForUse: {},
-
-      setForceForUse: function(usage, force) {
-        this._forceForUse[usage] = force;
-      },
-
-      getForceForUse: function(usage) {
-        return this._forceForUse[usage] || nsIAudioManager.FORCE_NONE;
-      }
-    };
-  }
-});
-
 XPCOMUtils.defineLazyServiceGetter(this, "gRadioInterfaceLayer",
                                    "@mozilla.org/ril;1",
                                    "nsIRadioInterfaceLayer");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gPowerManagerService",
                                    "@mozilla.org/power/powermanagerservice;1",
                                    "nsIPowerManagerService");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSystemMessenger",
                                    "@mozilla.org/system-message-internal;1",
                                    "nsISystemMessagesInternal");
 
+XPCOMUtils.defineLazyServiceGetter(this, "gAudioService",
+                                   "@mozilla.org/telephony/audio-service;1",
+                                   "nsITelephonyAudioService");
+
 XPCOMUtils.defineLazyServiceGetter(this, "gGonkMobileConnectionService",
                                    "@mozilla.org/mobileconnection/mobileconnectionservice;1",
                                    "nsIGonkMobileConnectionService");
 
 XPCOMUtils.defineLazyGetter(this, "gPhoneNumberUtils", function() {
   let ns = {};
   Cu.import("resource://gre/modules/PhoneNumberUtils.jsm", ns);
   return ns.PhoneNumberUtils;
@@ -274,36 +244,25 @@ TelephonyService.prototype = {
       this._numActiveCall--;
     }
     this._isActiveCall[aCall.clientId][aCall.callIndex] = active;
   },
 
   _updateAudioState: function(aAudioState) {
     switch (aAudioState) {
       case RIL.AUDIO_STATE_NO_CALL:
-        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_NORMAL;
+        gAudioService.setPhoneState(nsITelephonyAudioService.PHONE_STATE_NORMAL);
         break;
-
       case RIL.AUDIO_STATE_INCOMING:
-        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_RINGTONE;
+        gAudioService.setPhoneState(nsITelephonyAudioService.PHONE_STATE_RINGTONE);
         break;
-
       case RIL.AUDIO_STATE_IN_CALL:
-        gAudioManager.phoneState = nsIAudioManager.PHONE_STATE_IN_CALL;
-        if (this.speakerEnabled) {
-          gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION,
-                                       nsIAudioManager.FORCE_SPEAKER);
-        }
+        gAudioService.setPhoneState(nsITelephonyAudioService.PHONE_STATE_IN_CALL);
         break;
     }
-
-    if (DEBUG) {
-      debug("Put audio system into " + AUDIO_STATE_NAME[aAudioState] + ": " +
-            aAudioState + ", result is: " + gAudioManager.phoneState);
-    }
   },
 
   _convertRILCallState: function(aState) {
     switch (aState) {
       case RIL.CALL_STATE_UNKNOWN:
         return nsITelephonyService.CALL_STATE_UNKNOWN;
       case RIL.CALL_STATE_ACTIVE:
         return nsITelephonyService.CALL_STATE_CONNECTED;
@@ -1085,38 +1044,29 @@ TelephonyService.prototype = {
         aCallback.notifyError(response.errorMsg);
       } else {
         aCallback.notifySuccess();
       }
     });
   },
 
   get microphoneMuted() {
-    return gAudioManager.microphoneMuted;
+    return gAudioService.microphoneMuted;
   },
 
   set microphoneMuted(aMuted) {
-    if (aMuted == this.microphoneMuted) {
-      return;
-    }
-    gAudioManager.microphoneMuted = aMuted;
+    gAudioService.microphoneMuted = aMuted;
   },
 
   get speakerEnabled() {
-    let force = gAudioManager.getForceForUse(nsIAudioManager.USE_COMMUNICATION);
-    return (force == nsIAudioManager.FORCE_SPEAKER);
+    return gAudioService.speakerEnabled;
   },
 
   set speakerEnabled(aEnabled) {
-    if (aEnabled == this.speakerEnabled) {
-      return;
-    }
-    let force = aEnabled ? nsIAudioManager.FORCE_SPEAKER :
-                           nsIAudioManager.FORCE_NONE;
-    gAudioManager.setForceForUse(nsIAudioManager.USE_COMMUNICATION, force);
+    gAudioService.speakerEnabled = aEnabled;
   },
 
   /**
    * nsIGonkTelephonyService interface.
    */
 
   notifyAudioStateChanged: function(aClientId, aState) {
     this._audioStates[aClientId] = aState;
--- a/dom/telephony/moz.build
+++ b/dom/telephony/moz.build
@@ -48,18 +48,21 @@ IPDL_SOURCES += [
     'ipc/PTelephonyRequest.ipdl',
     'ipc/TelephonyTypes.ipdlh'
 ]
 
 if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk' and CONFIG['MOZ_B2G_RIL']:
     XPCSHELL_TESTS_MANIFESTS += ['test/xpcshell/xpcshell.ini']
     XPIDL_SOURCES += [
         'nsIGonkTelephonyService.idl',
+        'nsITelephonyAudioService.idl',
     ]
     EXTRA_COMPONENTS += [
+        'gonk/TelephonyAudioService.js',
+        'gonk/TelephonyAudioService.manifest',
         'gonk/TelephonyService.js',
         'gonk/TelephonyService.manifest',
     ]
 
 FAIL_ON_WARNINGS = True
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
new file mode 100644
--- /dev/null
+++ b/dom/telephony/nsITelephonyAudioService.idl
@@ -0,0 +1,31 @@
+/* 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/. */
+
+#include "nsISupports.idl"
+
+[scriptable, uuid(a452c565-fbac-4056-a8f4-feee9b33aa6e)]
+interface nsITelephonyAudioService : nsISupports
+{
+  const long PHONE_STATE_NORMAL   = 0;
+  const long PHONE_STATE_RINGTONE = 1;
+  const long PHONE_STATE_IN_CALL  = 2;
+
+  /**
+   * Microphone muted?
+   */
+  attribute boolean microphoneMuted;
+
+  /**
+   * Speaker enabled?
+   */
+  attribute boolean speakerEnabled;
+
+  /**
+   * Set the phone's audio mode.
+   *
+   * @param state
+   *        One of the nsITelephonyAudioService::PHONE_STATE_* values.
+   */
+  void setPhoneState(in long state);
+};
--- a/layout/style/test/Makefile.in
+++ b/layout/style/test/Makefile.in
@@ -18,10 +18,11 @@ ifdef COMPILE_ENVIRONMENT
 css_properties.js: host_ListCSSProperties$(HOST_BIN_SUFFIX) css_properties_like_longhand.js Makefile
 	$(RM) $@
 	./host_ListCSSProperties$(HOST_BIN_SUFFIX) > $@
 	cat $(srcdir)/css_properties_like_longhand.js >> $@
 
 GARBAGE += css_properties.js
 TEST_FILES := css_properties.js
 TEST_DEST = $(DEPTH)/_tests/testing/mochitest/tests/$(relativesrcdir)
+TEST_TARGET := misc
 INSTALL_TARGETS += TEST
 endif
--- a/layout/style/test/moz.build
+++ b/layout/style/test/moz.build
@@ -1,14 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
+HAS_MISC_RULE = True
+
 HostSimplePrograms([
     'host_ListCSSProperties',
 ])
 
 MOCHITEST_MANIFESTS += [
     'chrome/mochitest.ini',
     'css-visited/mochitest.ini',
     'mochitest.ini',
--- a/mobile/android/base/SharedPreferencesHelper.java
+++ b/mobile/android/base/SharedPreferencesHelper.java
@@ -22,16 +22,20 @@ import java.util.HashMap;
 /**
  * Helper class to get, set, and observe Android Shared Preferences.
  */
 public final class SharedPreferencesHelper
              implements GeckoEventListener
 {
     public static final String LOGTAG = "GeckoAndSharedPrefs";
 
+    // Calculate this once, at initialization. isLoggable is too expensive to
+    // have in-line in each log call.
+    private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
+
     private enum Scope {
         APP("app"),
         PROFILE("profile"),
         GLOBAL("global");
 
         public final String key;
 
         private Scope(String key) {
@@ -209,17 +213,17 @@ public final class SharedPreferencesHelp
         public ChangeListener(final Scope scope, final String branch, final String profileName) {
             this.scope = scope;
             this.branch = branch;
             this.profileName = profileName;
         }
 
         @Override
         public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, String key) {
-            if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
+            if (logVerbose) {
                 Log.v(LOGTAG, "Got onSharedPreferenceChanged");
             }
             try {
                 final JSONObject msg = new JSONObject();
                 msg.put("scope", this.scope.key);
                 msg.put("branch", this.branch);
                 msg.put("profileName", this.profileName);
                 msg.put("key", key);
@@ -274,29 +278,29 @@ public final class SharedPreferencesHelp
     }
 
     @Override
     public void handleMessage(String event, JSONObject message) {
         // Everything here is synchronous and serial, so we need not worry about
         // overwriting an in-progress response.
         try {
             if (event.equals("SharedPreferences:Set")) {
-                if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
+                if (logVerbose) {
                     Log.v(LOGTAG, "Got SharedPreferences:Set message.");
                 }
                 handleSet(message);
             } else if (event.equals("SharedPreferences:Get")) {
-                if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
+                if (logVerbose) {
                     Log.v(LOGTAG, "Got SharedPreferences:Get message.");
                 }
                 JSONObject obj = new JSONObject();
                 obj.put("values", handleGet(message));
                 EventDispatcher.sendResponse(message, obj);
             } else if (event.equals("SharedPreferences:Observe")) {
-                if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
+                if (logVerbose) {
                     Log.v(LOGTAG, "Got SharedPreferences:Observe message.");
                 }
                 handleObserve(message);
             } else {
                 Log.e(LOGTAG, "SharedPreferencesHelper got unexpected message " + event);
                 return;
             }
         } catch (JSONException e) {
--- a/mobile/android/base/distribution/Distribution.java
+++ b/mobile/android/base/distribution/Distribution.java
@@ -19,17 +19,16 @@ import java.net.URI;
 import java.net.URISyntaxException;
 import java.net.UnknownHostException;
 import java.util.Collections;
 import java.util.Enumeration;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
 import java.util.Queue;
-import java.util.Scanner;
 import java.util.concurrent.ConcurrentLinkedQueue;
 import java.util.jar.JarEntry;
 import java.util.jar.JarInputStream;
 import java.util.zip.ZipEntry;
 import java.util.zip.ZipFile;
 
 import javax.net.ssl.SSLException;
 
@@ -37,16 +36,17 @@ import org.apache.http.protocol.HTTP;
 import org.json.JSONArray;
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.GeckoEvent;
 import org.mozilla.gecko.GeckoSharedPrefs;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.mozglue.RobocopTarget;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.ThreadUtils;
 
 import android.app.Activity;
 import android.content.Context;
 import android.content.SharedPreferences;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -281,17 +281,17 @@ public class Distribution {
     public DistributionDescriptor getDescriptor() {
         File descFile = getDistributionFile("preferences.json");
         if (descFile == null) {
             // Logging and existence checks are handled in getDistributionFile.
             return null;
         }
 
         try {
-            JSONObject all = new JSONObject(getFileContents(descFile));
+            JSONObject all = new JSONObject(FileUtils.getFileContents(descFile));
 
             if (!all.has("Global")) {
                 Log.e(LOGTAG, "Distribution preferences.json has no Global entry!");
                 return null;
             }
 
             return new DistributionDescriptor(all.getJSONObject("Global"));
 
@@ -309,17 +309,17 @@ public class Distribution {
     public JSONArray getBookmarks() {
         File bookmarks = getDistributionFile("bookmarks.json");
         if (bookmarks == null) {
             // Logging and existence checks are handled in getDistributionFile.
             return null;
         }
 
         try {
-            return new JSONArray(getFileContents(bookmarks));
+            return new JSONArray(FileUtils.getFileContents(bookmarks));
         } catch (IOException e) {
             Log.e(LOGTAG, "Error getting bookmarks", e);
             Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
             return null;
         } catch (JSONException e) {
             Log.e(LOGTAG, "Error parsing bookmarks.json", e);
             Telemetry.HistogramAdd(HISTOGRAM_CODE_CATEGORY, CODE_CATEGORY_MALFORMED_DISTRIBUTION);
             return null;
@@ -734,29 +734,16 @@ public class Distribution {
         }
         File system = getSystemDistributionDir();
         if (system.exists()) {
             return this.distributionDir = system;
         }
         return null;
     }
 
-    // Shortcut to slurp a file without messing around with streams.
-    private String getFileContents(File file) throws IOException {
-        Scanner scanner = null;
-        try {
-            scanner = new Scanner(file, "UTF-8");
-            return scanner.useDelimiter("\\A").next();
-        } finally {
-            if (scanner != null) {
-                scanner.close();
-            }
-        }
-    }
-
     private String getDataDir() {
         return context.getApplicationInfo().dataDir;
     }
 
     private File getSystemDistributionDir() {
         return new File("/system/" + context.getPackageName() + "/distribution");
     }
 
--- a/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
+++ b/mobile/android/base/fxa/activities/FxAccountStatusFragment.java
@@ -33,16 +33,17 @@ import android.os.Handler;
 import android.preference.CheckBoxPreference;
 import android.preference.EditTextPreference;
 import android.preference.Preference;
 import android.preference.Preference.OnPreferenceChangeListener;
 import android.preference.Preference.OnPreferenceClickListener;
 import android.preference.PreferenceCategory;
 import android.preference.PreferenceScreen;
 import android.text.TextUtils;
+import android.text.format.DateUtils;
 
 /**
  * A fragment that displays the status of an AndroidFxAccount.
  * <p>
  * The owning activity is responsible for providing an AndroidFxAccount at
  * appropriate times.
  */
 public class FxAccountStatusFragment
@@ -81,16 +82,17 @@ public class FxAccountStatusFragment
   protected CheckBoxPreference bookmarksPreference;
   protected CheckBoxPreference historyPreference;
   protected CheckBoxPreference tabsPreference;
   protected CheckBoxPreference passwordsPreference;
 
   protected EditTextPreference deviceNamePreference;
   protected Preference syncServerPreference;
   protected Preference morePreference;
+  protected Preference syncNowPreference;
 
   protected volatile AndroidFxAccount fxAccount;
   // The contract is: when fxAccount is non-null, then clientsDataDelegate is
   // non-null.  If violated then an IllegalStateException is thrown.
   protected volatile SharedPreferencesClientsDataDelegate clientsDataDelegate;
 
   // Used to post delayed sync requests.
   protected Handler handler;
@@ -162,16 +164,20 @@ public class FxAccountStatusFragment
 
     deviceNamePreference = (EditTextPreference) ensureFindPreference("device_name");
     deviceNamePreference.setOnPreferenceChangeListener(this);
 
     syncServerPreference = ensureFindPreference("sync_server");
     morePreference = ensureFindPreference("more");
     morePreference.setOnPreferenceClickListener(this);
 
+    syncNowPreference = ensureFindPreference("sync_now");
+    syncNowPreference.setEnabled(true);
+    syncNowPreference.setOnPreferenceClickListener(this);
+
     if (HardwareUtils.hasMenuButton()) {
       syncCategory.removePreference(morePreference);
     }
   }
 
   /**
    * We intentionally don't refresh here. Our owning activity is responsible for
    * providing an AndroidFxAccount to our refresh method in its onResume method.
@@ -224,16 +230,23 @@ public class FxAccountStatusFragment
       return true;
     }
 
     if (preference == morePreference) {
       getActivity().openOptionsMenu();
       return true;
     }
 
+    if (preference == syncNowPreference) {
+      if (fxAccount != null) {
+        FirefoxAccounts.requestSync(fxAccount.getAndroidAccount(), FirefoxAccounts.FORCE, null, null);
+      }
+      return true;
+    }
+
     return false;
   }
 
   protected Bundle getExtrasForAccount() {
     final Bundle extras = new Bundle();
     final ExtendedJSONObject o = new ExtendedJSONObject();
     o.put(FxAccountAbstractSetupActivity.JSON_KEY_AUTH, fxAccount.getAccountServerURI());
     final ExtendedJSONObject services = new ExtendedJSONObject();
@@ -245,16 +258,17 @@ public class FxAccountStatusFragment
 
   protected void setCheckboxesEnabled(boolean enabled) {
     bookmarksPreference.setEnabled(enabled);
     historyPreference.setEnabled(enabled);
     tabsPreference.setEnabled(enabled);
     passwordsPreference.setEnabled(enabled);
     // Since we can't sync, we can't update our remote client record.
     deviceNamePreference.setEnabled(enabled);
+    syncNowPreference.setEnabled(enabled);
   }
 
   /**
    * Show at most one error preference, hiding all others.
    *
    * @param errorPreferenceToShow
    *          single error preference to show; if null, hide all error preferences
    */
@@ -465,16 +479,36 @@ public class FxAccountStatusFragment
     } finally {
       // No matter our state, we should update the checkboxes.
       updateSelectedEngines();
     }
 
     final String clientName = clientsDataDelegate.getClientName();
     deviceNamePreference.setSummary(clientName);
     deviceNamePreference.setText(clientName);
+
+    updateSyncNowPreference();
+  }
+
+  // This is a helper function similar to TabsAccessor.getLastSyncedString() to calculate relative "Last synced" time span.
+  private String getLastSyncedString(final long startTime) {
+    final CharSequence relativeTimeSpanString = DateUtils.getRelativeTimeSpanString(startTime);
+    return getActivity().getResources().getString(R.string.fxaccount_status_last_synced, relativeTimeSpanString);
+  }
+
+  protected void updateSyncNowPreference() {
+    final boolean currentlySyncing = fxAccount.isCurrentlySyncing();
+    syncNowPreference.setEnabled(!currentlySyncing);
+    if (currentlySyncing) {
+      syncNowPreference.setTitle(R.string.fxaccount_status_syncing);
+    } else {
+      syncNowPreference.setTitle(R.string.fxaccount_status_sync_now);
+    }
+    final String lastSynced = getLastSyncedString(fxAccount.getLastSyncedTimestamp());
+    syncNowPreference.setSummary(lastSynced);
   }
 
   protected void updateAuthServerPreference() {
     final String authServer = fxAccount.getAccountServerURI();
     final boolean shouldBeShown = ALWAYS_SHOW_AUTH_SERVER || !FxAccountConstants.DEFAULT_AUTH_SERVER_ENDPOINT.equals(authServer);
     final boolean currentlyShown = null != findPreference(authServerPreference.getKey());
     if (currentlyShown != shouldBeShown) {
       if (shouldBeShown) {
--- a/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
+++ b/mobile/android/base/fxa/authenticator/AndroidFxAccount.java
@@ -60,16 +60,18 @@ public class AndroidFxAccount {
 
   public static final int CURRENT_BUNDLE_VERSION = 2;
   public static final String BUNDLE_KEY_BUNDLE_VERSION = "version";
   public static final String BUNDLE_KEY_STATE_LABEL = "stateLabel";
   public static final String BUNDLE_KEY_STATE = "state";
 
   protected static final List<String> ANDROID_AUTHORITIES = Collections.unmodifiableList(Arrays.asList(BrowserContract.AUTHORITY));
 
+  private static final String PREF_KEY_LAST_SYNCED_TIMESTAMP = "lastSyncedTimestamp";
+
   protected final Context context;
   protected final AccountManager accountManager;
   protected final Account account;
 
   /**
    * Create an Android Firefox Account instance backed by an Android Account
    * instance.
    * <p>
@@ -560,9 +562,27 @@ public class AndroidFxAccount {
   public static Intent makeDeletedAccountIntent(final Context context, final Account account) {
     final Intent intent = new Intent(FxAccountConstants.ACCOUNT_DELETED_ACTION);
 
     intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION_KEY,
         Long.valueOf(FxAccountConstants.ACCOUNT_DELETED_INTENT_VERSION));
     intent.putExtra(FxAccountConstants.ACCOUNT_DELETED_INTENT_ACCOUNT_KEY, account.name);
     return intent;
   }
+
+  public void setLastSyncedTimestamp(long now) {
+    try {
+      getSyncPrefs().edit().putLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, now).commit();
+    } catch (Exception e) {
+      Logger.warn(LOG_TAG, "Got exception setting last synced time; ignoring.", e);
+    }
+  }
+
+  public long getLastSyncedTimestamp() {
+    final long neverSynced = -1L;
+    try {
+      return getSyncPrefs().getLong(PREF_KEY_LAST_SYNCED_TIMESTAMP, neverSynced);
+    } catch (Exception e) {
+      Logger.warn(LOG_TAG, "Got exception getting last synced time; ignoring.", e);
+      return neverSynced;
+    }
+  }
 }
--- a/mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java
+++ b/mobile/android/base/fxa/sync/FxAccountSchedulePolicy.java
@@ -100,16 +100,17 @@ public class FxAccountSchedulePolicy imp
     this.context.getContentResolver();
     Logger.info(LOG_TAG, "Scheduling periodic sync for " + intervalSeconds + ".");
     ContentResolver.addPeriodicSync(account, authority, Bundle.EMPTY, intervalSeconds);
     POLL_INTERVAL_CURRENT_SEC = intervalSeconds;
   }
 
   @Override
   public void onSuccessfulSync(int otherClientsCount) {
+    this.account.setLastSyncedTimestamp(System.currentTimeMillis());
     // This undoes the change made in observeBackoffMillis -- once we hit backoff we'll
     // periodically sync at the backoff duration, but as soon as we succeed we'll switch
     // into the client-count-dependent interval.
     long interval = (otherClientsCount > 0) ? POLL_INTERVAL_MULTI_DEVICE_SEC : POLL_INTERVAL_SINGLE_DEVICE_SEC;
     requestPeriodicSync(interval);
   }
 
   @Override
--- a/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
+++ b/mobile/android/base/fxa/sync/FxAccountSyncAdapter.java
@@ -429,38 +429,41 @@ public class FxAccountSyncAdapter extend
    * This should be replaced with a full {@link FxAccountAuthenticator}-based
    * token implementation.
    */
   @Override
   public void onPerformSync(final Account account, final Bundle extras, final String authority, ContentProviderClient provider, final SyncResult syncResult) {
     Logger.setThreadLogTag(FxAccountConstants.GLOBAL_LOG_TAG);
     Logger.resetLogging();
 
+    final Context context = getContext();
+    final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
+
     Logger.info(LOG_TAG, "Syncing FxAccount" +
         " account named like " + Utils.obfuscateEmail(account.name) +
         " for authority " + authority +
         " with instance " + this + ".");
 
+    Logger.info(LOG_TAG, "Account last synced at: " + fxAccount.getLastSyncedTimestamp());
+
+    if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
+      fxAccount.dump();
+    }
+
     final EnumSet<FirefoxAccounts.SyncHint> syncHints = FirefoxAccounts.getHintsToSyncFromBundle(extras);
     FirefoxAccounts.logSyncHints(syncHints);
 
     // This applies even to forced syncs, but only on success.
     if (this.lastSyncRealtimeMillis > 0L &&
         (this.lastSyncRealtimeMillis + MINIMUM_SYNC_DELAY_MILLIS) > SystemClock.elapsedRealtime()) {
       Logger.info(LOG_TAG, "Not syncing FxAccount " + Utils.obfuscateEmail(account.name) +
                            ": minimum interval not met.");
       return;
     }
 
-    final Context context = getContext();
-    final AndroidFxAccount fxAccount = new AndroidFxAccount(context, account);
-    if (FxAccountConstants.LOG_PERSONAL_INFORMATION) {
-      fxAccount.dump();
-    }
-
     // Pickle in a background thread to avoid strict mode warnings.
     ThreadPool.run(new Runnable() {
       @Override
       public void run() {
         try {
           AccountPickler.pickle(fxAccount, FxAccountConstants.ACCOUNT_PICKLE_FILENAME);
         } catch (Exception e) {
           // Should never happen, but we really don't want to die in a background thread.
--- a/mobile/android/base/locales/en-US/sync_strings.dtd
+++ b/mobile/android/base/locales/en-US/sync_strings.dtd
@@ -182,16 +182,18 @@
 
 <!ENTITY fxaccount_update_credentials_header 'Sign in'>
 <!ENTITY fxaccount_update_credentials_button_label 'Sign in'>
 <!ENTITY fxaccount_update_credentials_unknown_error 'Could not sign in'>
 
 <!ENTITY fxaccount_status_header2 'Firefox Account'>
 <!ENTITY fxaccount_status_signed_in_as 'Signed in as'>
 <!ENTITY fxaccount_status_auth_server 'Account server'>
+<!ENTITY fxaccount_status_sync_now 'Sync now'>
+<!ENTITY fxaccount_status_syncing 'Syncing...'>
 <!ENTITY fxaccount_status_device_name 'Device name'>
 <!ENTITY fxaccount_status_sync_server 'Sync server'>
 <!ENTITY fxaccount_status_sync '&syncBrand.shortName.label;'>
 <!ENTITY fxaccount_status_sync_enabled '&syncBrand.shortName.label;: enabled'>
 <!ENTITY fxaccount_status_needs_verification2 'Your account needs to be verified. Tap to resend verification email.'>
 <!ENTITY fxaccount_status_needs_credentials 'Cannot connect. Tap to sign in.'>
 <!ENTITY fxaccount_status_needs_upgrade 'You need to upgrade &brandShortName; to sign in.'>
 <!ENTITY fxaccount_status_needs_master_sync_automatically_enabled '&syncBrand.shortName.label; is set up, but not syncing automatically. Toggle “Auto-sync data” in Android Settings &gt; Data Usage.'>
--- a/mobile/android/base/resources/layout/fxaccount_confirm_account.xml
+++ b/mobile/android/base/resources/layout/fxaccount_confirm_account.xml
@@ -40,17 +40,17 @@
             style="@style/FxAccountButton"
             android:text="@string/fxaccount_back_to_browsing" />
 
         <TextView
             android:id="@+id/resend_confirmation_email_link"
             style="@style/FxAccountLinkItem"
             android:text="@string/fxaccount_confirm_account_resend_email" />
 
-       	<TextView
+        <TextView
             android:id="@+id/change_confirmation_email_link"
             style="@style/FxAccountLinkItem"
             android:text="@string/fxaccount_confirm_account_change_email" />
 
         <LinearLayout style="@style/FxAccountSpacer" />
 
         <ImageView
             style="@style/FxAccountIcon"
--- a/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
+++ b/mobile/android/base/resources/xml/fxaccount_status_prefscreen.xml
@@ -50,16 +50,24 @@
         <Preference
             android:editable="false"
             android:icon="@drawable/fxaccount_sync_error"
             android:key="needs_account_enabled"
             android:layout="@layout/fxaccount_status_error_preference"
             android:persistent="false"
             android:title="@string/fxaccount_status_needs_account_enabled" />
 
+        <Preference
+            android:editable="false"
+            android:key="sync_now"
+            android:defaultValue=""
+            android:persistent="false"
+            android:title="Sync now"
+            android:summary="" />
+
         <CheckBoxPreference
             android:key="bookmarks"
             android:persistent="false"
             android:title="@string/fxaccount_status_bookmarks" />
         <CheckBoxPreference
             android:key="history"
             android:persistent="false"
             android:title="@string/fxaccount_status_history" />
--- a/mobile/android/base/strings.xml.in
+++ b/mobile/android/base/strings.xml.in
@@ -91,16 +91,17 @@
   <string name="history_week_section">&history_week_section2;</string>
   <string name="history_older_section">&history_older_section2;</string>
 
   <string name="share">&share;</string>
   <string name="share_title">&share_title;</string>
   <string name="share_image_failed">&share_image_failed;</string>
   <string name="save_as_pdf">&save_as_pdf;</string>
   <string name="find_in_page">&find_in_page;</string>
+  <string name="find_matchcase">&find_matchcase;</string>
   <string name="desktop_mode">&desktop_mode;</string>
   <string name="page">&page;</string>
   <string name="tools">&tools;</string>
 
   <string name="find_text">&find_text;</string>
   <string name="find_prev">&find_prev;</string>
   <string name="find_next">&find_next;</string>
   <string name="find_close">&find_close;</string>
--- a/mobile/android/base/sync/SyncConfiguration.java
+++ b/mobile/android/base/sync/SyncConfiguration.java
@@ -32,17 +32,16 @@ public class SyncConfiguration {
     public EditorBranch(SyncConfiguration config, String prefix) {
       if (!prefix.endsWith(".")) {
         throw new IllegalArgumentException("No trailing period in prefix.");
       }
       this.prefix = prefix;
       this.editor = config.getEditor();
     }
 
-    @Override
     public void apply() {
       // Android <=r8 SharedPreferences.Editor does not contain apply() for overriding.
       this.editor.commit();
     }
 
     @Override
     public Editor clear() {
       this.editor = this.editor.clear();
@@ -81,17 +80,16 @@ public class SyncConfiguration {
     @Override
     public Editor putString(String key, String value) {
       this.editor = this.editor.putString(prefix + key, value);
       return this;
     }
 
     // Not marking as Override, because Android <= 10 doesn't have
     // putStringSet. Neither can we implement it.
-    @Override
     public Editor putStringSet(String key, Set<String> value) {
       throw new RuntimeException("putStringSet not available.");
     }
 
     @Override
     public Editor remove(String key) {
       this.editor = this.editor.remove(prefix + key);
       return this;
@@ -157,17 +155,16 @@ public class SyncConfiguration {
 
     @Override
     public String getString(String key, String defValue) {
       return config.getPrefs().getString(prefix + key, defValue);
     }
 
     // Not marking as Override, because Android <= 10 doesn't have
     // getStringSet. Neither can we implement it.
-    @Override
     public Set<String> getStringSet(String key, Set<String> defValue) {
       throw new RuntimeException("getStringSet not available.");
     }
 
     @Override
     public void registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener) {
       config.getPrefs().registerOnSharedPreferenceChangeListener(listener);
     }
--- a/mobile/android/base/sync/net/BaseResource.java
+++ b/mobile/android/base/sync/net/BaseResource.java
@@ -60,17 +60,17 @@ import ch.boye.httpclientandroidlib.util
 public class BaseResource implements Resource {
   private static final String ANDROID_LOOPBACK_IP = "10.0.2.2";
 
   private static final int MAX_TOTAL_CONNECTIONS     = 20;
   private static final int MAX_CONNECTIONS_PER_ROUTE = 10;
 
   private boolean retryOnFailedRequest = true;
 
-  public static final boolean rewriteLocalhost = true;
+  public static boolean rewriteLocalhost = true;
 
   private static final String LOG_TAG = "BaseResource";
 
   protected final URI uri;
   protected BasicHttpContext context;
   protected DefaultHttpClient client;
   public    ResourceDelegate delegate;
   protected HttpRequestBase request;
--- a/mobile/android/base/tests/JavascriptTest.java
+++ b/mobile/android/base/tests/JavascriptTest.java
@@ -6,16 +6,21 @@ import org.mozilla.gecko.tests.helpers.J
 import org.mozilla.gecko.tests.helpers.JavascriptMessageParser;
 
 import android.util.Log;
 
 public class JavascriptTest extends BaseTest {
     private static final String LOGTAG = "JavascriptTest";
     private static final String EVENT_TYPE = JavascriptBridge.EVENT_TYPE;
 
+    // Calculate these once, at initialization. isLoggable is too expensive to
+    // have in-line in each log call.
+    private static final boolean logDebug   = Log.isLoggable(LOGTAG, Log.DEBUG);
+    private static final boolean logVerbose = Log.isLoggable(LOGTAG, Log.VERBOSE);
+
     private final String javascriptUrl;
 
     public JavascriptTest(String javascriptUrl) {
         super();
         this.javascriptUrl = javascriptUrl;
     }
 
     public void testJavascript() throws Exception {
@@ -34,38 +39,38 @@ public class JavascriptTest extends Base
         final String url = getAbsoluteUrl(StringHelper.getHarnessUrlForJavascript(javascriptUrl));
         mAsserter.dumpLog("Loading JavaScript test from " + url);
         loadUrl(url);
 
         final JavascriptMessageParser testMessageParser =
                 new JavascriptMessageParser(mAsserter, false);
         try {
             while (!testMessageParser.isTestFinished()) {
-                if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
+                if (logVerbose) {
                     Log.v(LOGTAG, "Waiting for " + EVENT_TYPE);
                 }
                 String data = expecter.blockForEventData();
-                if (Log.isLoggable(LOGTAG, Log.VERBOSE)) {
+                if (logVerbose) {
                     Log.v(LOGTAG, "Got event with data '" + data + "'");
                 }
 
                 JSONObject o = new JSONObject(data);
                 String innerType = o.getString("innerType");
                 if (!"progress".equals(innerType)) {
                     throw new Exception("Unexpected event innerType " + innerType);
                 }
 
                 String message = o.getString("message");
                 if (message == null) {
                     throw new Exception("Progress message must not be null");
                 }
                 testMessageParser.logMessage(message);
             }
 
-            if (Log.isLoggable(LOGTAG, Log.DEBUG)) {
+            if (logDebug) {
                 Log.d(LOGTAG, "Got test finished message");
             }
         } finally {
             expecter.unregisterListener();
             mAsserter.dumpLog("Unregistered listener for " + EVENT_TYPE);
         }
     }
 }
--- a/mobile/android/base/util/FileUtils.java
+++ b/mobile/android/base/util/FileUtils.java
@@ -4,16 +4,17 @@
 
 package org.mozilla.gecko.util;
 
 import android.util.Log;
 
 import java.io.File;
 import java.io.IOException;
 import java.io.FilenameFilter;
+import java.util.Scanner;
 
 import org.mozilla.gecko.mozglue.RobocopTarget;
 
 public class FileUtils {
     private static final String LOGTAG= "GeckoFileUtils";
     /*
     * A basic Filter for checking a filename and age.
     **/
@@ -76,9 +77,22 @@ public class FileUtils {
                     Log.i(LOGTAG, "Error deleting " + fileDelete.getPath(), ex);
                 }
             }
         }
 
         // Even if this is a dir, it should now be empty and delete should work
         return file.delete();
     }
+
+    // Shortcut to slurp a file without messing around with streams.
+    public static String getFileContents(File file) throws IOException {
+        Scanner scanner = null;
+        try {
+            scanner = new Scanner(file, "UTF-8");
+            return scanner.useDelimiter("\\A").next();
+        } finally {
+            if (scanner != null) {
+                scanner.close();
+            }
+        }
+    }
 }
--- a/mobile/android/chrome/content/browser.js
+++ b/mobile/android/chrome/content/browser.js
@@ -6902,17 +6902,17 @@ OverscrollController.prototype = {
 };
 
 var SearchEngines = {
   _contextMenuId: null,
   PREF_SUGGEST_ENABLED: "browser.search.suggest.enabled",
   PREF_SUGGEST_PROMPTED: "browser.search.suggest.prompted",
 
   // Shared preference key used for search activity default engine.
-  PREF_SEARCH_ACTIVITY_ENGINE_KEY: "search.engines.default",
+  PREF_SEARCH_ACTIVITY_ENGINE_KEY: "search.engines.defaultname",
 
   init: function init() {
     Services.obs.addObserver(this, "SearchEngines:Add", false);
     Services.obs.addObserver(this, "SearchEngines:GetVisible", false);
     Services.obs.addObserver(this, "SearchEngines:Remove", false);
     Services.obs.addObserver(this, "SearchEngines:RestoreDefaults", false);
     Services.obs.addObserver(this, "SearchEngines:SetDefault", false);
     Services.obs.addObserver(this, "browser-search-engine-modified", false);
@@ -7042,44 +7042,17 @@ var SearchEngines = {
   },
 
   migrateSearchActivityDefaultPref: function migrateSearchActivityDefaultPref() {
     Services.search.init(() => this._setSearchActivityDefaultPref(Services.search.defaultEngine));
   },
 
   // Updates the search activity pref when the default engine changes.
   _setSearchActivityDefaultPref: function _setSearchActivityDefaultPref(engine) {
-    // Helper function copied from nsSearchService.js. This is the logic that is used
-    // to create file names for search plugin XML serialized to disk.
-    function sanitizeName(aName) {
-      const maxLength = 60;
-      const minLength = 1;
-      let name = aName.toLowerCase();
-      name = name.replace(/\s+/g, "-");
-      name = name.replace(/[^-a-z0-9]/g, "");
-
-      if (name.length < minLength) {
-        // Well, in this case, we're kinda screwed. In this case, the search service
-        // generates a random file name, so to do this the right way, we'd need
-        // to open up search.json and see what file name is stored.
-        Cu.reportError("Couldn't create search plugin file name from engine name: " + aName);
-        return null;
-      }
-
-      // Force max length.
-      return name.substring(0, maxLength);
-    }
-
-    let identifier = engine.identifier;
-    if (identifier === null) {
-      // The identifier will be null for non-built-in engines. In this case, we need to
-      // figure out an identifier to store from the engine name.
-      identifier = sanitizeName(engine.name);
-    }
-    SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, identifier);
+    SharedPreferences.forApp().setCharPref(this.PREF_SEARCH_ACTIVITY_ENGINE_KEY, engine.name);
   },
 
   // Display context menu listing names of the search engines available to be added.
   displaySearchEnginesList: function displaySearchEnginesList(aData) {
     let data = JSON.parse(aData);
     let tab = BrowserApp.getTabForId(data.tabId);
 
     if (!tab)
--- a/mobile/android/config/mozconfigs/common
+++ b/mobile/android/config/mozconfigs/common
@@ -10,17 +10,17 @@
 # much slower and we didn't want to slow down developers builds.
 # Has no effect when MOZ_ENABLE_SZIP is not set in mobile/android/confvars.sh.
 MOZ_SZIP_FLAGS="-D auto -f auto"
 
 ac_add_options --enable-elf-hack
 
 ANDROID_NDK_VERSION="r8e"
 ANDROID_NDK_VERSION_32BIT="r8c"
-ANDROID_SDK_VERSION="20"
+ANDROID_SDK_VERSION="21"
 
 # Build Fennec
 ac_add_options --enable-application=mobile/android
 
 if test `uname -m` = 'x86_64'; then
   ac_add_options --with-android-ndk="$topsrcdir/android-ndk"
   ac_add_options --with-android-sdk="$topsrcdir/android-sdk-linux/platforms/android-$ANDROID_SDK_VERSION"
 else
--- a/mobile/android/config/tooltool-manifests/android-armv6/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android-armv6/releng.manifest
@@ -1,18 +1,18 @@
 [
 {
 "size": 78706854,
 "digest": "8ff42509ecebfd7e20f8fac9987ed2b2c04942641eead674ee66f74014c5153f1c20080cd3ccb243af76ca7432df3c3f5b5ae08a478fd2817e62661a4edb437c",
 "algorithm": "sha512",
 "filename": "android-ndk.tar.bz2"
 },
 {
-"size": 207966812,
-"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
+"size": 227988048,
+"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
 "algorithm": "sha512",
 "filename": "android-sdk.tar.xz"
 },
 {
 "size": 336,
 "digest": "3336af2e7106654be09ae10b19f981162584ede0888abe295c45fe6e10f52d460a0a94b8bef0518f1419fcd82939b723123e122897599edce9cc240757424890",
 "algorithm": "sha512",
 "filename": "setup.sh"
--- a/mobile/android/config/tooltool-manifests/android-x86/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android-x86/releng.manifest
@@ -1,18 +1,18 @@
 [
 {
 "size": 80729933,
 "digest": "ccd13527c0ba3979f4030eae713e6529e916a701b9b16a371256d92618e914fb0fd2ba70547efca0e93d02010c89eeb7c222d40a9b6f26fab80911386cdfecc7",
 "algorithm": "sha512",
 "filename": "android-ndk.tar.bz2"
 },
 {
-"size": 207966812,
-"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
+"size": 227988048,
+"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
 "algorithm": "sha512",
 "filename": "android-sdk.tar.xz"
 },
 {
 "size": 336,
 "digest": "3336af2e7106654be09ae10b19f981162584ede0888abe295c45fe6e10f52d460a0a94b8bef0518f1419fcd82939b723123e122897599edce9cc240757424890",
 "algorithm": "sha512",
 "filename": "setup.sh"
--- a/mobile/android/config/tooltool-manifests/android/releng.manifest
+++ b/mobile/android/config/tooltool-manifests/android/releng.manifest
@@ -1,18 +1,18 @@
 [
 {
 "size": 78706854,
 "digest": "8ff42509ecebfd7e20f8fac9987ed2b2c04942641eead674ee66f74014c5153f1c20080cd3ccb243af76ca7432df3c3f5b5ae08a478fd2817e62661a4edb437c",
 "algorithm": "sha512",
 "filename": "android-ndk.tar.bz2"
 },
 {
-"size": 207966812,
-"digest": "9f6d50e5e67e6a6784dea3b5573178a869b017d2d0c7588af9eb53fdd23c7d1bd6f775f07a9ad1510ba36bf1608d21baa4e26e92afe61e190429870a6371b97d",
+"size": 227988048,
+"digest": "c84db0abd1f4fda1bb38292ef561e211e1e6b99586764fd8cf0829fa4d0c6a605eb21e1eb5462465fcca64749d48e22ac1b13029e2623bbdfe103801f5ef1411",
 "algorithm": "sha512",
 "filename": "android-sdk.tar.xz"
 },
 {
 "size": 336,
 "digest": "3336af2e7106654be09ae10b19f981162584ede0888abe295c45fe6e10f52d460a0a94b8bef0518f1419fcd82939b723123e122897599edce9cc240757424890",
 "algorithm": "sha512",
 "filename": "setup.sh"
--- a/mobile/android/gradle/Makefile.in
+++ b/mobile/android/gradle/Makefile.in
@@ -1,19 +1,21 @@
 # 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/.
 
 gradle := \
+  local.properties.in \
   gradle.properties.in \
   $(NULL)
 
 gradle_PATH := $(CURDIR)
 gradle_FLAGS += -Dtopsrcdir=$(abspath $(topsrcdir))
 gradle_FLAGS += -Dtopobjdir=$(abspath $(DEPTH))
+gradle_FLAGS += -DANDROID_SDK_ROOT=$(ANDROID_SDK_ROOT)
 gradle_KEEP_PATH := 1
 PP_TARGETS += gradle
 
 wrapper_FILES := \
   build.gradle \
   settings.gradle \
   gradle/wrapper/gradle-wrapper.jar \
   gradle/wrapper/gradle-wrapper.properties \
--- a/mobile/android/gradle/build.gradle
+++ b/mobile/android/gradle/build.gradle
@@ -9,15 +9,15 @@ ext {
 }
 
 buildscript {
     repositories {
         jcenter()
     }
 
     dependencies {
-        classpath 'com.android.tools.build:gradle:0.12.2'
+        classpath 'com.android.tools.build:gradle:0.14.1'
     }
 }
 
 repositories {
     jcenter()
 }
index 8c0fb64a8698b08ecc4158d828ca593c4928e9dd..3d0dee6e8edfecc92e04653ec780de06f7b34f8b
GIT binary patch
literal 51017
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
--- a/mobile/android/gradle/gradle/wrapper/gradle-wrapper.properties
+++ b/mobile/android/gradle/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,6 @@
-#Wed Apr 10 15:27:10 PDT 2013
+#Thu Nov 13 14:57:51 PST 2014
 distributionBase=GRADLE_USER_HOME
 distributionPath=wrapper/dists
 zipStoreBase=GRADLE_USER_HOME
 zipStorePath=wrapper/dists
-distributionUrl=http\://services.gradle.org/distributions/gradle-1.12-all.zip
+distributionUrl=https\://services.gradle.org/distributions/gradle-2.1-bin.zip
new file mode 100644
--- /dev/null
+++ b/mobile/android/gradle/local.properties.in
@@ -0,0 +1,2 @@
+#filter substitution
+sdk.dir=@ANDROID_SDK_ROOT@
--- a/mobile/android/search/java/org/mozilla/search/Constants.java
+++ b/mobile/android/search/java/org/mozilla/search/Constants.java
@@ -12,14 +12,9 @@ package org.mozilla.search;
 
 /**
  * Key should not be stored here. For more info on storing keys, see
  * https://github.com/ericedens/FirefoxSearch/issues/3
  */
 public class Constants {
 
     public static final String ABOUT_BLANK = "about:blank";
-
-    // TODO: Localize this with region.properties (or a similar solution). See bug 1065306.
-    public static final String DEFAULT_ENGINE_IDENTIFIER = "yahoo";
-
-    public static final String PREF_SEARCH_ENGINE_KEY = "search.engines.default";
 }
--- a/mobile/android/search/java/org/mozilla/search/SearchActivity.java
+++ b/mobile/android/search/java/org/mozilla/search/SearchActivity.java
@@ -5,16 +5,17 @@
 package org.mozilla.search;
 
 import org.mozilla.gecko.GeckoAppShell;
 import org.mozilla.gecko.LocaleAware;
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.Telemetry;
 import org.mozilla.gecko.TelemetryContract;
 import org.mozilla.gecko.db.BrowserContract.SearchHistory;
+import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.gecko.health.BrowserHealthRecorder;
 import org.mozilla.search.autocomplete.SearchBar;
 import org.mozilla.search.autocomplete.SuggestionsFragment;
 import org.mozilla.search.providers.SearchEngine;
 import org.mozilla.search.providers.SearchEngineManager;
 import org.mozilla.search.providers.SearchEngineManager.SearchEngineCallback;
 
 import android.content.AsyncQueryHandler;
@@ -96,17 +97,17 @@ public class SearchActivity extends Loca
         GeckoAppShell.ensureCrashHandling();
 
         super.onCreate(savedInstanceState);
         setContentView(R.layout.search_activity_main);
 
         suggestionsFragment = (SuggestionsFragment) getSupportFragmentManager().findFragmentById(R.id.suggestions);
         postSearchFragment = (PostSearchFragment)  getSupportFragmentManager().findFragmentById(R.id.postsearch);
 
-        searchEngineManager = new SearchEngineManager(this);
+        searchEngineManager = new SearchEngineManager(this, Distribution.init(this));
         searchEngineManager.setChangeCallback(this);
 
         // Initialize the fragments with the selected search engine.
         searchEngineManager.getEngine(this);
 
         queryHandler = new AsyncQueryHandler(getContentResolver()) {};
 
         searchBar = (SearchBar) findViewById(R.id.search_bar);
@@ -274,30 +275,37 @@ public class SearchActivity extends Loca
             return;
         }
 
         // engine will only be null if startSearch is called before the getEngine
         // call in onCreate is completed.
         searchEngineManager.getEngine(new SearchEngineCallback() {
             @Override
             public void execute(SearchEngine engine) {
-                postSearchFragment.startSearch(engine, query);
+                // TODO: If engine is null, we should show an error message.
+                if (engine != null) {
+                    postSearchFragment.startSearch(engine, query);
+                }
             }
         });
     }
 
     /**
      * This method is called when we fetch the current engine in onCreate,
      * as well as whenever the current engine changes. This method will only
      * ever be called on the main thread.
      *
      * @param engine The current search engine.
      */
     @Override
     public void execute(SearchEngine engine) {
+        // TODO: If engine is null, we should show an error message.
+        if (engine == null) {
+            return;
+        }
         this.engine = engine;
         suggestionsFragment.setEngine(engine);
         searchBar.setEngine(engine);
     }
 
     /**
      * Animates search suggestion item to fill the results view area.
      *
--- a/mobile/android/search/java/org/mozilla/search/providers/SearchEngine.java
+++ b/mobile/android/search/java/org/mozilla/search/providers/SearchEngine.java
@@ -47,17 +47,19 @@ public class SearchEngine {
     // head of a web page. The actual CSS is inserted at `%s`.
     private static final String STYLE_INJECTION_SCRIPT =
             "javascript:(function(){" +
                     "var tag=document.createElement('style');" +
                     "tag.type='text/css';" +
                     "document.getElementsByTagName('head')[0].appendChild(tag);" +
                     "tag.innerText='%s'})();";
 
+    // The Gecko search identifier. This will be null for engines that don't ship with the locale.
     private final String identifier;
+
     private String shortName;
     private String iconURL;
 
     // Ordered list of preferred results URIs.
     private final List<Uri> resultsUris = new ArrayList<Uri>();
     private Uri suggestUri;
 
     /**
@@ -184,17 +186,19 @@ public class SearchEngine {
      * HACKS! We'll need to replace this with endpoints that return the correct content.
      *
      * Retrieve a JS snippet, in bookmarklet style, that can be used
      * to modify the results page.
      */
     public String getInjectableJs() {
         final String css;
 
-        if (identifier.equals("bing")) {
+        if (identifier == null) {
+            css = "";
+        } else if (identifier.equals("bing")) {
             css = "#mHeader{display:none}#contentWrapper{margin-top:0}";
         } else if (identifier.equals("google")) {
             css = "#sfcnt,#top_nav{display:none}";
         } else if (identifier.equals("yahoo")) {
             css = "#nav,#header{display:none}";
         } else {
             css = "";
         }
@@ -242,31 +246,31 @@ public class SearchEngine {
     /**
      * Create a uri string that can be used to fetch the results page.
      *
      * @param query The user's query. This method will escape and encode the query.
      */
     public String resultsUriForQuery(String query) {
         final Uri resultsUri = getResultsUri();
         if (resultsUri == null) {
-            Log.e(LOG_TAG, "No results URL for search engine: " + identifier);
+            Log.e(LOG_TAG, "No results URL for search engine: " + shortName);
             return "";
         }
         final String template = Uri.decode(resultsUri.toString());
         return paramSubstitution(template, Uri.encode(query));
     }
 
     /**
      * Create a uri string to fetch autocomplete suggestions.
      *
      * @param query The user's query. This method will escape and encode the query.
      */
     public String getSuggestionTemplate(String query) {
         if (suggestUri == null) {
-            Log.e(LOG_TAG, "No suggestions template for search engine: " + identifier);
+            Log.e(LOG_TAG, "No suggestions template for search engine: " + shortName);
             return "";
         }
         final String template = Uri.decode(suggestUri.toString());
         return paramSubstitution(template, Uri.encode(query));
     }
 
     /**
      * @return Preferred results URI.
--- a/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
+++ b/mobile/android/search/java/org/mozilla/search/providers/SearchEngineManager.java
@@ -1,211 +1,420 @@
 /* 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.search.providers;
 
 import android.content.Context;
 import android.content.SharedPreferences;
-import android.os.AsyncTask;
 import android.text.TextUtils;
 import android.util.Log;
 
+import org.json.JSONException;
+import org.json.JSONObject;
 import org.mozilla.gecko.AppConstants;
 import org.mozilla.gecko.BrowserLocaleManager;
 import org.mozilla.gecko.GeckoProfile;
 import org.mozilla.gecko.GeckoSharedPrefs;
+import org.mozilla.gecko.R;
+import org.mozilla.gecko.util.FileUtils;
 import org.mozilla.gecko.util.GeckoJarReader;
+import org.mozilla.gecko.util.RawResource;
+import org.mozilla.gecko.util.ThreadUtils;
+import org.mozilla.gecko.distribution.Distribution;
 import org.mozilla.search.Constants;
 import org.xmlpull.v1.XmlPullParserException;
 
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.IOException;
 import java.io.InputStream;
 import java.io.InputStreamReader;
 import java.util.ArrayList;
 import java.util.List;
 import java.util.Locale;
 
 public class SearchEngineManager implements SharedPreferences.OnSharedPreferenceChangeListener {
-    private static final String LOG_TAG = "SearchEngineManager";
+    private static final String LOG_TAG = "GeckoSearchEngineManager";
+
+    // Gecko pref that defines the name of the default search engine.
+    private static final String PREF_GECKO_DEFAULT_ENGINE = "browser.search.defaultenginename";
+
+    // Key for shared preference that stores default engine name.
+    private static final String PREF_DEFAULT_ENGINE_KEY = "search.engines.defaultname";
 
     private Context context;
+    private Distribution distribution;
     private SearchEngineCallback changeCallback;
     private SearchEngine engine;
 
     public static interface SearchEngineCallback {
         public void execute(SearchEngine engine);
     }
 
-    public SearchEngineManager(Context context) {
+    public SearchEngineManager(Context context, Distribution distribution) {
         this.context = context;
+        this.distribution = distribution;
         GeckoSharedPrefs.forApp(context).registerOnSharedPreferenceChangeListener(this);
     }
 
+    /**
+     * Sets a callback to be called when the default engine changes.
+     *
+     * @param callback SearchEngineCallback to be called after the search engine
+     *                 changed. This will run on the UI thread.
+     *                 Note: callback may be called with null engine.
+     */
     public void setChangeCallback(SearchEngineCallback changeCallback) {
         this.changeCallback = changeCallback;
     }
 
     /**
      * Perform an action with the user's default search engine.
      *
      * @param callback The callback to be used with the user's default search engine. The call
      *                 may be sync or async; if the call is async, it will be called on the
      *                 ui thread.
      */
     public void getEngine(SearchEngineCallback callback) {
         if (engine != null) {
             callback.execute(engine);
         } else {
-            getEngineFromPrefs(callback);
+            getDefaultEngine(callback);
         }
     }
 
     public void destroy() {
         GeckoSharedPrefs.forApp(context).unregisterOnSharedPreferenceChangeListener(this);
         context = null;
+        distribution = null;
         changeCallback = null;
         engine = null;
     }
 
+    private int ignorePreferenceChange = 0;
+
     @Override
     public void onSharedPreferenceChanged(final SharedPreferences sharedPreferences, final String key) {
-        if (!TextUtils.equals(Constants.PREF_SEARCH_ENGINE_KEY, key)) {
+        if (!TextUtils.equals(PREF_DEFAULT_ENGINE_KEY, key)) {
+            return;
+        }
+
+        if (ignorePreferenceChange > 0) {
+            ignorePreferenceChange--;
             return;
         }
-        getEngineFromPrefs(changeCallback);
+
+        getDefaultEngine(changeCallback);
+    }
+
+    /**
+     * Runs a SearchEngineCallback on the main thread.
+     */
+    private void runCallback(final SearchEngine engine, final SearchEngineCallback callback) {
+        ThreadUtils.postToUiThread(new Runnable() {
+            @Override
+            public void run() {
+                // Cache engine for future calls to getEngine.
+                SearchEngineManager.this.engine = engine;
+                callback.execute(engine);
+            }
+        });
+    }
+
+    /**
+     * This method finds and creates the default search engine. It will first look for
+     * the default engine name, then create the engine from that name.
+     *
+     * To find the default engine name, we first look in shared preferences, then
+     * the distribution (if one exists), and finally fall back to the localized default.
+     *
+     * @param callback SearchEngineCallback to be called after successfully looking
+     *                 up the search engine. This will run on the UI thread.
+     *                 Note: callback may be called with null engine.
+     */
+    private void getDefaultEngine(final SearchEngineCallback callback) {
+        // This runnable is posted to the background thread.
+        distribution.addOnDistributionReadyCallback(new Runnable() {
+            @Override
+            public void run() {
+                // First look for a default name stored in shared preferences.
+                String name = GeckoSharedPrefs.forApp(context).getString(PREF_DEFAULT_ENGINE_KEY, null);
+
+                if (name != null) {
+                    Log.d(LOG_TAG, "Found default engine name in SharedPreferences: " + name);
+                } else {
+                    // First, look for the default search engine in a distribution.
+                    name = getDefaultEngineNameFromDistribution();
+                    if (name == null) {
+                        // Otherwise, get the default engine that we ship.
+                        name = getDefaultEngineNameFromLocale();
+                    }
+
+                    // Store the default engine name for the future.
+                    // Increment an 'ignore' counter so that this preference change
+                    // won'tcause getDefaultEngine to be called again.
+                    ignorePreferenceChange++;
+                    GeckoSharedPrefs.forApp(context)
+                                    .edit()
+                                    .putString(PREF_DEFAULT_ENGINE_KEY, name)
+                                    .apply();
+                }
+
+                final SearchEngine engine = createEngineFromName(name);
+                runCallback(engine, callback);
+            }
+        });
     }
 
     /**
-     * Look up the current search engine in shared preferences.
-     * Creates a SearchEngine instance and caches it for use on the main thread.
+     * Looks for a default search engine included in a distribution.
+     * This method must be called after the distribution is ready.
      *
-     * @param callback a SearchEngineCallback to be called after successfully looking
-     *                 up the search engine. This will run on the UI thread.
+     * @return search engine name.
      */
-    private void getEngineFromPrefs(final SearchEngineCallback callback) {
-        final AsyncTask<Void, Void, SearchEngine> task = new AsyncTask<Void, Void, SearchEngine>() {
-            @Override
-            protected SearchEngine doInBackground(Void... params) {
-                String identifier = GeckoSharedPrefs.forApp(context).getString(Constants.PREF_SEARCH_ENGINE_KEY, null);
-                if (!TextUtils.isEmpty(identifier)) {
-                    try {
-                        return createEngine(identifier);
-                    } catch (IllegalArgumentException e) {
-                        Log.e(LOG_TAG, "Exception creating search engine from pref. Falling back to default engine.", e);
-                    }
+    private String getDefaultEngineNameFromDistribution() {
+        if (!distribution.exists()) {
+            return null;
+        }
+
+        final File prefFile = distribution.getDistributionFile("preferences.json");
+        if (prefFile == null) {
+            return null;
+        }
+
+        try {
+            final JSONObject all = new JSONObject(FileUtils.getFileContents(prefFile));
+
+            // First, check to see if there's a locale-specific override.
+            final String languageTag = BrowserLocaleManager.getLanguageTag(Locale.getDefault());
+            final String overridesKey = "LocalizablePreferences." + languageTag;
+            if (all.has(overridesKey)) {
+                final JSONObject overridePrefs = all.getJSONObject(overridesKey);
+                if (overridePrefs.has(PREF_GECKO_DEFAULT_ENGINE)) {
+                    Log.d(LOG_TAG, "Found default engine name in distribution LocalizablePreferences override.");
+                    return overridePrefs.getString(PREF_GECKO_DEFAULT_ENGINE);
                 }
-
-                try {
-                    return createEngine(Constants.DEFAULT_ENGINE_IDENTIFIER);
-                } catch (IllegalArgumentException e) {
-                    Log.e(LOG_TAG, "Exception creating search engine from default identifier. " +
-                            "This will happen if the locale doesn't contain the default search plugin.", e);
-                }
-
-                return null;
             }
 
-            @Override
-            protected void onPostExecute(SearchEngine engine) {
-                if (engine != null) {
-                    // Only touch engine on the main thread.
-                    SearchEngineManager.this.engine = engine;
-                    if (callback != null) {
-                        callback.execute(engine);
-                    }
+            // Next, check to see if there's a non-override default pref.
+            if (all.has("LocalizablePreferences")) {
+                final JSONObject localizablePrefs = all.getJSONObject("LocalizablePreferences");
+                if (localizablePrefs.has(PREF_GECKO_DEFAULT_ENGINE)) {
+                    Log.d(LOG_TAG, "Found default engine name in distribution LocalizablePreferences.");
+                    return localizablePrefs.getString(PREF_GECKO_DEFAULT_ENGINE);
                 }
             }
-        };
-        task.execute();
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error getting search engine name from preferences.json", e);
+        } catch (JSONException e) {
+            Log.e(LOG_TAG, "Error parsing preferences.json", e);
+        }
+        return null;
+    }
+
+    /**
+     * Looks for the default search engine shipped in the locale.
+     *
+     * @return search engine name.
+     */
+    private String getDefaultEngineNameFromLocale() {
+        try {
+            final JSONObject browsersearch = new JSONObject(RawResource.getAsString(context, R.raw.browsersearch));
+            if (browsersearch.has("default")) {
+                Log.d(LOG_TAG, "Found default engine name in browsersearch.json.");
+                return browsersearch.getString("default");
+            }
+        } catch (IOException e) {
+            Log.e(LOG_TAG, "Error getting search engine name from browsersearch.json", e);
+        } catch (JSONException e) {
+            Log.e(LOG_TAG, "Error parsing browsersearch.json", e);
+        }
+        return null;
     }
 
     /**
-     * Creates a list of SearchEngine instances from all available open search plugins.
-     * This method does disk I/O, call it from a background thread.
+     * Creates a SearchEngine instance from an engine name.
      *
-     * @return List of SearchEngine instances
+     * To create the engine, we first try to find the search plugin in the distribution
+     * (if one exists), followed by the localized plugins we ship with the browser, and
+     * then finally third-party plugins that are installed in the profile directory.
+     *
+     * This method must be called after the distribution is ready.
+     *
+     * @param name The search engine name (e.g. "Google" or "Amazon.com")
+     * @return SearchEngine instance for name.
      */
-    public List<SearchEngine> getAllEngines() {
-        // First try to read the engine list from the jar.
-        InputStream in = getInputStreamFromJar("list.txt");
+    private SearchEngine createEngineFromName(String name) {
+        // First, look in the distribution.
+        SearchEngine engine = createEngineFromDistribution(name);
+
+        // Second, look in the jar for plugins shipped with the locale.
+        if (engine == null) {
+            engine = createEngineFromLocale(name);
+        }
+
+        // Finally, look in the profile for third-party plugins.
+        if (engine == null) {
+            engine = createEngineFromProfile(name);
+        }
+
+        if (engine == null) {
+            Log.e(LOG_TAG, "Could not create search engine from name: " + name);
+        }
+
+        return engine;
+    }
 
-        final List<SearchEngine> list = new ArrayList<SearchEngine>();
+    /**
+     * Creates a SearchEngine instance for a distribution search plugin.
+     *
+     * This method iterates through the distribution searchplugins directory,
+     * creating SearchEngine instances until it finds one with the right name.
+     *
+     * This method must be called after the distribution is ready.
+     *
+     * @param name Search engine name.
+     * @return SearchEngine instance for name.
+     */
+    private SearchEngine createEngineFromDistribution(String name) {
+        if (!distribution.exists()) {
+            return null;
+        }
+
+        final File pluginsDir = distribution.getDistributionFile("searchplugins");
+        if (pluginsDir == null) {
+            return null;
+        }
+
+        final File[] files = (new File(pluginsDir, "common")).listFiles();
+        return createEngineFromFileList(files, name);
+    }
+
+    /**
+     * Creates a SearchEngine instance for a search plugin shipped in the locale.
+     *
+     * This method reads the list of search plugin file names from list.txt, then
+     * iterates through the files, creating SearchEngine instances until it finds one
+     * with the right name. Unfortunately, we need to do this because there is no
+     * other way to map the search engine "name" to the file for the search plugin.
+     *
+     * @param name Search engine name.
+     * @return SearchEngine instance for name.
+     */
+    private SearchEngine createEngineFromLocale(String name) {
+        final InputStream in = getInputStreamFromSearchPluginsJar("list.txt");
         InputStreamReader isr = null;
 
         try {
             isr = new InputStreamReader(in);
             BufferedReader br = new BufferedReader(isr);
             String identifier;
             while ((identifier = br.readLine()) != null) {
-                list.add(createEngine(identifier));
+                final InputStream pluginIn = getInputStreamFromSearchPluginsJar(identifier + ".xml");
+                final SearchEngine engine = createEngineFromInputStream(identifier, pluginIn);
+                if (engine != null && engine.getName().equals(name)) {
+                    return engine;
+                }
             }
         } catch (IOException e) {
-            throw new IllegalStateException("Error creating all search engines from list.txt");
+            Log.e(LOG_TAG, "Error creating shipped search engine from name: " + name, e);
         } finally {
             if (isr != null) {
                 try {
                     isr.close();
                 } catch (IOException e) {
                     // Ignore.
                 }
             }
             try {
                 in.close();
             } catch (IOException e) {
                 // Ignore.
             }
         }
-        return list;
+        return null;
+    }
+
+    /**
+     * Creates a SearchEngine instance for a search plugin in the profile directory.
+     *
+     * This method iterates through the profile searchplugins directory, creating
+     * SearchEngine instances until it finds one with the right name.
+     *
+     * @param name Search engine name.
+     * @return SearchEngine instance for name.
+     */
+    private SearchEngine createEngineFromProfile(String name) {
+        final File pluginsDir = GeckoProfile.get(context).getFile("searchplugins");
+        if (pluginsDir == null) {
+            return null;
+        }
+
+        final File[] files = pluginsDir.listFiles();
+        return createEngineFromFileList(files, name);
     }
 
     /**
-     * Creates a SearchEngine instance from an open search plugin.
-     * This method does disk I/O, call it from a background thread.
+     * This method iterates through an array of search plugin files, creating
+     * SearchEngine instances until it finds one with the right name.
      *
-     * @param identifier search engine identifier (e.g. "google")
-     * @return SearchEngine instance for identifier
+     * @param files Array of search plugin files.
+     * @param name Search engine name.
+     * @return SearchEngine instance for name.
      */
-    private SearchEngine createEngine(String identifier) {
-        InputStream in = getInputStreamFromJar(identifier + ".xml");
+    private SearchEngine createEngineFromFileList(File[] files, String name) {
+        for (int i = 0; i < files.length; i++) {
+            try {
+                final FileInputStream fis = new FileInputStream(files[i]);
+                final SearchEngine engine = createEngineFromInputStream(null, fis);
+                if (engine != null && engine.getName().equals(name)) {
+                    return engine;
+                }
+            } catch (IOException e) {
+                Log.e(LOG_TAG, "Error creating earch engine from name: " + name, e);
+            }
+        }
+        return null;
+    }
 
-        if (in == null) {
-            in = getEngineFromProfile(identifier);
-        }
-
-        if (in == null) {
-            throw new IllegalArgumentException("Couldn't find search engine for identifier: " + identifier);
-        }
-
+    /**
+     * Creates a SearchEngine instance from an InputStream.
+     *
+     * This method closes the stream after it is done reading it.
+     *
+     * @param identifier Seach engine identifier. This only exists for search engines that
+     *                   ship with the default set of engines in the locale.
+     * @param in InputStream for search plugin XML file.
+     * @return SearchEngine instance.
+     */
+    private SearchEngine createEngineFromInputStream(String identifier, InputStream in) {
         try {
             try {
                 return new SearchEngine(identifier, in);
             } finally {
                 in.close();
             }
         } catch (IOException | XmlPullParserException e) {
             Log.e(LOG_TAG, "Exception creating search engine", e);
         }
 
         return null;
     }
 
     /**
-     * Reads a file from the searchplugins directory in the Gecko jar. This will only work
-     * if the search activity is built as part of mozilla-central.
+     * Reads a file from the searchplugins directory in the Gecko jar.
      *
-     * @param fileName name of the file to read
-     * @return InputStream for file
+     * @param fileName name of the file to read.
+     * @return InputStream for file.
      */
-    private InputStream getInputStreamFromJar(String fileName) {
+    private InputStream getInputStreamFromSearchPluginsJar(String fileName) {
         final Locale locale = Locale.getDefault();
 
         // First, try a file path for the full locale.
         final String languageTag = BrowserLocaleManager.getLanguageTag(locale);
         String url = getSearchPluginsJarURL(languageTag, fileName);
 
         InputStream in = GeckoJarReader.getStream(url);
         if (in != null) {
@@ -223,37 +432,19 @@ public class SearchEngineManager impleme
         }
 
         // Finally, fall back to en-US.
         url = getSearchPluginsJarURL("en-US", fileName);
         return GeckoJarReader.getStream(url);
     }
 
     /**
-     * Gets the jar URL for a file in the searchplugins directory
+     * Gets the jar URL for a file in the searchplugins directory.
      *
-     * @param locale String representing the Gecko locale (e.g. "en-US")
-     * @param fileName name of the file to read
-     * @return URL for jar file
+     * @param locale String representing the Gecko locale (e.g. "en-US").
+     * @param fileName The name of the file to read.
+     * @return URL for jar file.
      */
     private String getSearchPluginsJarURL(String locale, String fileName) {
         final String path = "!/chrome/" + locale + "/locale/" + locale + "/browser/searchplugins/" + fileName;
         return "jar:jar:file://" + context.getPackageResourcePath() + "!/" + AppConstants.OMNIJAR_NAME + path;
     }
-
-    /**
-     * Opens the search plugin XML file from the searchplugins directory in the Gecko profile.
-     *
-     * @param identifier
-     * @return InputStream for search plugin file
-     */
-    private InputStream getEngineFromProfile(String identifier) {
-        final File f = GeckoProfile.get(context).getFile("searchplugins/" + identifier + ".xml");
-        if (f.exists()) {
-            try {
-                return new FileInputStream(f);
-            } catch (FileNotFoundException e) {
-                Log.e(LOG_TAG, "Exception getting search engine from profile", e);
-            }
-        }
-        return null;
-    }
 }
--- a/mobile/android/services/strings.xml.in
+++ b/mobile/android/services/strings.xml.in
@@ -170,16 +170,19 @@
 <string name="fxaccount_update_credentials_header">&fxaccount_update_credentials_header;</string>
 <string name="fxaccount_update_credentials_button_label">&fxaccount_update_credentials_button_label;</string>
 <string name="fxaccount_update_credentials_unknown_error">&fxaccount_update_credentials_unknown_error;</string>
 
 <string name="fxaccount_status_activity_label">&syncBrand.shortName.label;</string>
 <string name="fxaccount_status_header">&fxaccount_status_header2;</string>
 <string name="fxaccount_status_signed_in_as">&fxaccount_status_signed_in_as;</string>
 <string name="fxaccount_status_auth_server">&fxaccount_status_auth_server;</string>
+<string name="fxaccount_status_sync_now">&fxaccount_status_sync_now;</string>
+<string name="fxaccount_status_syncing">&fxaccount_status_syncing;</string>
+<string name="fxaccount_status_last_synced">&remote_tabs_last_synced;</string>
 <string name="fxaccount_status_device_name">&fxaccount_status_device_name;</string>
 <string name="fxaccount_status_sync_server">&fxaccount_status_sync_server;</string>
 <string name="fxaccount_status_sync">&fxaccount_status_sync;</string>
 <string name="fxaccount_status_sync_enabled">&fxaccount_status_sync_enabled;</string>
 <string name="fxaccount_status_needs_verification">&fxaccount_status_needs_verification2;</string>
 <string name="fxaccount_status_needs_credentials">&fxaccount_status_needs_credentials;</string>
 <string name="fxaccount_status_needs_upgrade">&fxaccount_status_needs_upgrade;</string>
 <string name="fxaccount_status_needs_master_sync_automatically_enabled">&fxaccount_status_needs_master_sync_automatically_enabled;</string>
@@ -211,11 +214,8 @@
 <string name="fxaccount_sync_sign_in_error_notification_title">&fxaccount_sync_sign_in_error_notification_title2;</string>
 <string name="fxaccount_sync_sign_in_error_notification_text">&fxaccount_sync_sign_in_error_notification_text2;</string>
 
 <!-- Remove Account -->
 <string name="fxaccount_remove_account_dialog_title">&fxaccount_remove_account_dialog_title;</string>
 <string name="fxaccount_remove_account_dialog_message">&fxaccount_remove_account_dialog_message;</string>
 <string name="fxaccount_remove_account_toast">&fxaccount_remove_account_toast;</string>
 <string name="fxaccount_remove_account_menu_item">&fxaccount_remove_account_menu_item;</string>
-
-<!-- Find-In-Page strings -->
-<string name="find_matchcase">&find_matchcase;</string>
--- a/mobile/android/thirdparty/org/mozilla/apache/commons/codec/binary/Hex.java
+++ b/mobile/android/thirdparty/org/mozilla/apache/commons/codec/binary/Hex.java
@@ -269,17 +269,19 @@ public class Hex implements BinaryEncode
      * @throws EncoderException
      *             Thrown if the given object is not a String or byte[]
      * @see #encodeHex(byte[])
      */
     public Object encode(Object object) throws EncoderException {
         try {
             byte[] byteArray = object instanceof String ? ((String) object).getBytes(getCharsetName()) : (byte[]) object;
             return encodeHex(byteArray);
-        } catch (ClassCastException | UnsupportedEncodingException e) {
+        } catch (ClassCastException e) {
+            throw new EncoderException(e.getMessage(), e);
+        } catch (UnsupportedEncodingException e) {
             throw new EncoderException(e.getMessage(), e);
         }
     }
 
     /**
      * Gets the charset name.
      * 
      * @return the charset name.
--- a/python/mozbuild/mozbuild/frontend/context.py
+++ b/python/mozbuild/mozbuild/frontend/context.py
@@ -496,16 +496,32 @@ VARIABLES = {
         and read the frontend file there. If there is no frontend file, an error
         is raised.
 
         Values are relative paths. They can be multiple directory levels
         above or below. Use ``..`` for parent directories and ``/`` for path
         delimiters.
         """, None),
 
+    'HAS_MISC_RULE': (bool, bool,
+        """Whether this directory should be traversed in the ``misc`` tier.
+
+        Many ``libs`` rules still exist in Makefile.in files. We highly prefer
+        that these rules exist in the ``misc`` tier/target so that they can be
+        executed concurrently during tier traversal (the ``misc`` tier is
+        fully concurrent).
+
+        Presence of this variable indicates that this directory should be
+        traversed by the ``misc`` tier.
+
+        Please note that converting ``libs`` rules to the ``misc`` tier must
+        be done with care, as there are many implicit dependencies that can
+        break the build in subtle ways.
+        """, 'misc'),
+
     'FINAL_TARGET_FILES': (HierarchicalStringList, list,
         """List of files to be installed into the application directory.
 
         ``FINAL_TARGET_FILES`` will copy (or symlink, if the platform supports it)
         the contents of its files to the directory specified by
         ``FINAL_TARGET`` (typically ``dist/bin``). Files that are destined for a
         subdirectory can be specified by accessing a field, or as a dict access.
         For example, to export ``foo.png`` to the top-level directory and
--- a/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/bar/moz.build
+++ b/python/mozbuild/mozbuild/test/frontend/data/traversal-simple/bar/moz.build
@@ -0,0 +1,1 @@
+HAS_MISC_RULE = True
--- a/python/mozbuild/mozbuild/test/frontend/test_emitter.py
+++ b/python/mozbuild/mozbuild/test/frontend/test_emitter.py
@@ -93,16 +93,18 @@ class TestEmitterBasic(unittest.TestCase
             self.assertIsInstance(o, DirectoryTraversal)
             self.assertEqual(o.test_dirs, [])
             self.assertTrue(os.path.isabs(o.context_main_path))
             self.assertEqual(len(o.context_all_paths), 1)
 
         reldirs = [o.relativedir for o in objs]
         self.assertEqual(reldirs, ['', 'foo', 'foo/biz', 'bar'])
 
+        self.assertEqual(objs[3].affected_tiers, {'misc'})
+
         dirs = [o.dirs for o in objs]
         self.assertEqual(dirs, [
             [
                 mozpath.join(reader.config.topsrcdir, 'foo'),
                 mozpath.join(reader.config.topsrcdir, 'bar')
             ], [
                 mozpath.join(reader.config.topsrcdir, 'foo', 'biz')
             ], [], []])