Merge m-c to inbound
authorWes Kocher <wkocher@mozilla.com>
Fri, 16 May 2014 17:15:49 -0700
changeset 183663 df3b325443700ccf6653e2084d6326ddd2cb66fc
parent 183662 4f0a7452db012f72fca1738cfa773162e145196b (current diff)
parent 183597 2893f60d5903bea55fe29e89abb539b53a8f15ba (diff)
child 183664 14a82a4878a84fd2e144aaf4753915983db56bf7
push id6844
push userphilringnalda@gmail.com
push dateSun, 18 May 2014 01:12:08 +0000
treeherderfx-team@41a54c8add09 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone32.0a1
Merge m-c to inbound
browser/devtools/webide/locales/Makefile.in
browser/devtools/webide/locales/jar.mn
browser/devtools/webide/locales/moz.build
--- 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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="e5f4683183a1dec2cfdb21b76509819977e9d09c"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <!-- 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="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <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="65fba428f8d76336b33ddd9e15900357953600ba">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
-  <project name="gaia" path="gaia" remote="mozillaorg" revision="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="fake-libdvm" path="dalvik" remote="b2g" revision="d50ae982b19f42f0b66d08b9eb306be81687869f"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <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="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <!-- Stock Android things -->
--- 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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <project name="platform_hardware_ril" path="hardware/ril" remote="b2g" revision="ca283b9db2b151d465cfd2e19346cf58fe89e413"/>
   <project name="platform_external_qemu" path="external/qemu" remote="b2g" revision="e5f4683183a1dec2cfdb21b76509819977e9d09c"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="dd924f92906085b831bf1cbbc7484d3c043d613c"/>
--- a/b2g/config/flame/sources.xml
+++ b/b2g/config/flame/sources.xml
@@ -13,17 +13,17 @@
   <remote fetch="https://git.mozilla.org/releases" name="mozillaorg"/>
   <!-- B2G specific things. -->
   <project name="platform_build" path="build" remote="b2g" revision="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <copyfile dest="Makefile" src="core/root.mk"/>
   </project>
   <project name="rilproxy" path="rilproxy" remote="b2g" revision="827214fcf38d6569aeb5c6d6f31cb296d1f09272"/>
   <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <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"/>
@@ -115,17 +115,17 @@
   <project name="platform/system/netd" path="system/netd" revision="ea8103eae5642621ca8202e00620f4ca954ed413"/>
   <project name="platform/system/security" path="system/security" revision="360f51f7af191316cd739f229db1c5f7233be063"/>
   <project name="platform/system/vold" path="system/vold" revision="153df4d067a4149c7d78f1c92fed2ce2bd6a272e"/>
   <default remote="caf" revision="jb_3.2" sync-j="4"/>
   <!-- Flame specific things -->
   <project name="device/generic/armv7-a-neon" path="device/generic/armv7-a-neon" revision="e8a318f7690092e639ba88891606f4183e846d3f"/>
   <project name="device/qcom/common" path="device/qcom/common" revision="34ed8345250bb97262d70a052217a92e83444ede"/>
   <project name="device-flame" path="device/t2m/flame" remote="b2g" revision="1b3322cfd1179ea11fa5083d600717b65e5923e6"/>
-  <project name="kernel/msm" path="kernel" revision="7158567fc83e7475f08db3adedc5df1ad6f54abd"/>
+  <project name="kernel/msm" path="kernel" revision="2051bfa495412f5077065e555ec662ba3d30e6e6"/>
   <project name="platform/bootable/recovery" path="bootable/recovery" revision="f2914eacee9120680a41463708bb6ee8291749fc"/>
   <project name="platform/external/bluetooth/bluedroid" path="external/bluetooth/bluedroid" revision="4b7ae991637a216d745e154cd49b4db6ca55a19e"/>
   <project name="platform/external/bluetooth/bluez" path="external/bluetooth/bluez" revision="f0689ac1914cdbc59e53bdc9edd9013dc157c299"/>
   <project name="platform/external/bluetooth/glib" path="external/bluetooth/glib" revision="dd925f76e4f149c3d5571b80e12f7e24bbe89c59"/>
   <project name="platform/external/dbus" path="external/dbus" revision="ea87119c843116340f5df1d94eaf8275e1055ae8"/>
   <project name="platform/external/libnfc-nci" path="external/libnfc-nci" revision="494c177966fdc31183a5f7af82dc9130f523da4b"/>
   <project name="platform/external/wpa_supplicant_8" path="external/wpa_supplicant_8" revision="320b05a5761eb2a4816f7529c91ea49422979b55"/>
   <project name="platform/frameworks/av" path="frameworks/av" revision="1df6dac11d7370a2fffca8e31d65b80f537faec5"/>
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,9 +1,9 @@
 {
     "git": {
         "git_revision": "", 
         "remote": "", 
         "branch": ""
     }, 
-    "revision": "7e5b2c297555985ac76b069361ff252fe176a018", 
+    "revision": "896e4c28c82da3f07a1910be2187201e2045ffd7", 
     "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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <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="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="d2eb6c7b6e1bc7643c17df2d9d9bcb1704d0b9ab"/>
--- 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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <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="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <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/inari/sources.xml
+++ b/b2g/config/inari/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="ics_chocolate_rb4.2" sync-j="4"/>
   <!-- Gonk specific things and forks -->
   <project name="platform_build" path="build" remote="b2g" revision="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <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="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
   <project name="platform/bionic" path="bionic" revision="cd5dfce80bc3f0139a56b58aca633202ccaee7f8"/>
--- a/b2g/config/leo/sources.xml
+++ b/b2g/config/leo/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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <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="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <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/config/mako/sources.xml
+++ b/b2g/config/mako/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="e6383e6e785cc3ea237e902beb1092f9aa88e29d">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <project name="moztt" path="external/moztt" remote="b2g" revision="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <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="2a165bebfa19b11b697837409f9550dd2917c46c">
     <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="17cc405ab6163528efe321e831bf0e72d1166a28"/>
+  <project name="gaia.git" path="gaia" remote="mozillaorg" revision="101c500903a2477f9de1ea5ce523b9e0be4d45d0"/>
   <project name="gonk-misc" path="gonk-misc" remote="b2g" revision="3b2aeeb5af3083c2f6f4b44beae0b4802566d482"/>
   <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="ce95d372e6d285725b96490afdaaf489ad8f9ca9"/>
   <project name="apitrace" path="external/apitrace" remote="apitrace" revision="8a4baf82a131a7853cf7e7f9cf74253927b2f355"/>
   <project name="gonk-patches" path="patches" remote="b2g" revision="223a2421006e8f5da33f516f6891c87cae86b0f6"/>
   <!-- Stock Android things -->
   <project name="platform/abi/cpp" path="abi/cpp" revision="6426040f1be4a844082c9769171ce7f5341a5528"/>
--- a/browser/devtools/webide/content/details.xhtml
+++ b/browser/devtools/webide/content/details.xhtml
@@ -1,16 +1,16 @@
 <?xml version="1.0" encoding="UTF-8"?>
 
 <!-- This Source Code Form is subject to the terms of the Mozilla Public
    - License, v. 2.0. If a copy of the MPL was not distributed with this
    - file, You can obtain one at http://mozilla.org/MPL/2.0/. -->
 
 <!DOCTYPE html [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/locale/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
   %webideDTD;
 ]>
 
 <html xmlns="http://www.w3.org/1999/xhtml">
   <head>
     <meta charset="utf8"/>
     <link rel="stylesheet" href="chrome://webide/skin/details.css" type="text/css"/>
     <script type="application/javascript;version=1.8" src="chrome://webide/content/details.js"></script>
--- a/browser/devtools/webide/content/jar.mn
+++ b/browser/devtools/webide/content/jar.mn
@@ -5,8 +5,14 @@
 webide.jar:
 %  content webide %content/
     content/webide.xul                (webide.xul)
     content/webide.js                 (webide.js)
     content/newapp.xul                (newapp.xul)
     content/newapp.js                 (newapp.js)
     content/details.xhtml             (details.xhtml)
     content/details.js                (details.js)
+
+# Temporarily include locales in content, until we're ready
+# to localize webide
+
+    content/webide.dtd                (../locales/en-US/webide.dtd)
+    content/webide.properties         (../locales/en-US/webide.properties)
--- a/browser/devtools/webide/content/newapp.xul
+++ b/browser/devtools/webide/content/newapp.xul
@@ -1,16 +1,16 @@
 <?xml version="1.0"?>
 
 <!-- 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/. -->
 
 <!DOCTYPE window [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/locale/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
   %webideDTD;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://webide/skin/newapp.css"?>
 
 <dialog id="webide:newapp" title="&newAppWindowTitle;"
   width="600" height="400"
--- a/browser/devtools/webide/content/webide.js
+++ b/browser/devtools/webide/content/webide.js
@@ -10,17 +10,17 @@ Cu.import("resource:///modules/devtools/
 
 const {devtools} = Cu.import("resource://gre/modules/devtools/Loader.jsm", {});
 const {require} = devtools;
 const {Services} = Cu.import("resource://gre/modules/Services.jsm");
 const {AppProjects} = require("devtools/app-manager/app-projects");
 const {Connection} = require("devtools/client/connection-manager");
 const {AppManager} = require("devtools/app-manager");
 
-const Strings = Services.strings.createBundle("chrome://webide/locale/webide.properties");
+const Strings = Services.strings.createBundle("chrome://webide/content/webide.properties");
 
 const HTML = "http://www.w3.org/1999/xhtml";
 
 window.addEventListener("load", function onLoad() {
   window.removeEventListener("load", onLoad);
   UI.init();
 });
 
--- a/browser/devtools/webide/content/webide.xul
+++ b/browser/devtools/webide/content/webide.xul
@@ -1,16 +1,16 @@
 <?xml version="1.0"?>
 
 <!-- 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/. -->
 
 <!DOCTYPE window [
-  <!ENTITY % webideDTD SYSTEM "chrome://webide/locale/webide.dtd" >
+  <!ENTITY % webideDTD SYSTEM "chrome://webide/content/webide.dtd" >
   %webideDTD;
 ]>
 
 <?xml-stylesheet href="chrome://global/skin/global.css"?>
 <?xml-stylesheet href="chrome://webide/skin/webide.css"?>
 
 <window id="webide"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
deleted file mode 100644
--- a/browser/devtools/webide/locales/Makefile.in
+++ /dev/null
@@ -1,5 +0,0 @@
-# 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/.
-
-DEFINES += -DAB_CD=$(AB_CD)
deleted file mode 100644
--- a/browser/devtools/webide/locales/jar.mn
+++ /dev/null
@@ -1,10 +0,0 @@
-#filter substitution
-# 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/.
-
-
-webide.jar:
-% locale webide @AB_CD@ %locale/
-  locale/webide.dtd           (%webide.dtd)
-  locale/webide.properties    (%webide.properties)
deleted file mode 100644
--- a/browser/devtools/webide/locales/moz.build
+++ /dev/null
@@ -1,7 +0,0 @@
-# -*- 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/.
-
-JAR_MANIFESTS += ['jar.mn']
\ No newline at end of file
--- a/browser/devtools/webide/moz.build
+++ b/browser/devtools/webide/moz.build
@@ -1,12 +1,11 @@
 # -*- 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/.
 
 PARALLEL_DIRS += [
     'content',
-    'locales',
     'themes',
 ]
 
--- a/browser/themes/windows/browser-aero.css
+++ b/browser/themes/windows/browser-aero.css
@@ -417,17 +417,21 @@
 /* ::::: private browsing indicator ::::: */
 
 @media (-moz-os-version: windows-vista),
        (-moz-os-version: windows-win7) {
   #TabsToolbar > .private-browsing-indicator {
     background-image: url("chrome://browser/skin/privatebrowsing-mask-tabstrip-XPVista7.png");
   }
 
-  #private-browsing-indicator-titlebar > .private-browsing-indicator {
+  /* We're intentionally using the titlebar asset here for fullscreen mode.
+   * See bug 1008183.
+   */
+  #private-browsing-indicator-titlebar > .private-browsing-indicator,
+  #main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
     background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png");
   }
 }
 
 @media (-moz-windows-glass) {
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
     top: 1px;
   }
--- a/browser/themes/windows/browser.css
+++ b/browser/themes/windows/browser.css
@@ -2850,27 +2850,32 @@ chatbox {
   display: block;
   position: absolute;
 }
 
 #main-window[privatebrowsingmode=temporary][tabsintitlebar] #private-browsing-indicator-titlebar > .private-browsing-indicator {
   display: block;
 }
 
-#main-window[privatebrowsingmode=temporary]:not([tabsintitlebar]) #TabsToolbar > .private-browsing-indicator {
+#main-window[privatebrowsingmode=temporary]:-moz-any([inFullscreen],:not([tabsintitlebar])) #TabsToolbar > .private-browsing-indicator {
   display: -moz-box;
 }
 
 #TabsToolbar > .private-browsing-indicator {
   background: url("chrome://browser/skin/privatebrowsing-mask-tabstrip.png") no-repeat center -3px;
   -moz-margin-start: 4px;
   width: 48px;
 }
 
-#private-browsing-indicator-titlebar > .private-browsing-indicator {
+/* Bug 1008183: We're intentionally using the titlebar asset here for fullscreen
+ * mode, since the tabstrip "mimics" the titlebar in that case with its own
+ * min/max/close window buttons.
+ */
+#private-browsing-indicator-titlebar > .private-browsing-indicator,
+#main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
   background: url("chrome://browser/skin/privatebrowsing-mask-titlebar.png") no-repeat center 0px;
   -moz-margin-end: 4px;
   width: 40px;
   height: 20px;
   position: relative;
 }
 
 %ifndef WINDOWS_AERO
@@ -2878,27 +2883,39 @@ chatbox {
   background-image: url("chrome://browser/skin/privatebrowsing-mask-tabstrip-XPVista7.png");
 }
 
 @media not all and (-moz-windows-classic) {
   #private-browsing-indicator-titlebar > .private-browsing-indicator {
     background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7-tall.png");
     height: 28px;
   }
+
+  /* We're intentionally using the titlebar asset here for fullscreen mode.
+   * See bug 1008183.
+   */
+  #main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
+    background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png");
+  }
+
   #main-window[sizemode="maximized"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
     top: -5px;
   }
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
     top: -1px;
   }
 }
 %endif
 
 @media (-moz-windows-classic) {
-  #private-browsing-indicator-titlebar > .private-browsing-indicator {
+  /* We're intentionally using the titlebar asset here for fullscreen mode.
+   * See bug 1008183.
+   */
+  #private-browsing-indicator-titlebar > .private-browsing-indicator,
+  #main-window[inFullscreen] #TabsToolbar > .private-browsing-indicator {
     background-image: url("chrome://browser/skin/privatebrowsing-mask-titlebar-XPVista7.png");
   }
   /**
    * We have to use top instead of background-position in this case, otherwise
    * the bottom of the indicator would get cut off by the bounds of the
    * private-browsing-indicator element.
    */
   #main-window[sizemode="normal"] > #titlebar > #titlebar-content > #titlebar-buttonbox-container > #private-browsing-indicator-titlebar > .private-browsing-indicator {
--- a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
+++ b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
@@ -139,29 +139,29 @@ this.Address = {
    */
   decode: function(data) {
     let str = EncodedStringValue.decode(data);
 
     let result;
     if (((result = str.match(this.REGEXP_DECODE_PLMN)) != null)
         || ((result = str.match(this.REGEXP_DECODE_IPV4)) != null)
         || ((result = str.match(this.REGEXP_DECODE_IPV6)) != null)
-        || ((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)) {
+        || (((result = str.match(this.REGEXP_DECODE_CUSTOM)) != null)
+            && (result[2] != "PLMN")
+            && (result[2] != "IPv4")
+            && (result[2] != "IPv6"))) {
       return {address: result[1], type: result[2]};
     }
 
     let type;
     if (str.match(this.REGEXP_NUM)) {
       type = "num";
     } else if (str.match(this.REGEXP_ALPHANUM)) {
       type = "alphanum";
-    } else if (str.indexOf("@") > 0) {
-      // E-mail should match the definition of `mailbox` as described in section
-      // 3.4 of RFC2822, but excluding the obsolete definitions as indicated by
-      // the "obs-" prefix. Here we match only a `@` character.
+    } else if (str.match(this.REGEXP_EMAIL)) {
       type = "email";
     } else {
       throw new WSP.CodeError("Address: invalid address");
     }
 
     return {address: str, type: type};
   },
 
@@ -174,17 +174,17 @@ this.Address = {
   encode: function(data, value) {
     if (!value || !value.type || !value.address) {
       throw new WSP.CodeError("Address: invalid value");
     }
 
     let str;
     switch (value.type) {
       case "email":
-        if (value.address.indexOf("@") > 0) {
+        if (value.address.match(this.REGEXP_EMAIL)) {
           str = value.address;
         }
         break;
       case "num":
         if (value.address.match(this.REGEXP_NUM)) {
           str = value.address;
         }
         break;
@@ -207,17 +207,17 @@ this.Address = {
         if (value.address.match(this.REGEXP_ENCODE_PLMN)) {
           str = value.address + "/TYPE=PLMN";
         }
         break;
       default:
         if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE)
             && value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) {
           str = value.address + "/TYPE=" + value.type;
-	}
+        }
         break;
     }
 
     if (!str) {
       throw new WSP.CodeError("Address: invalid value: " + JSON.stringify(value));
     }
 
     EncodedStringValue.encode(data, str);
@@ -229,48 +229,81 @@ this.Address = {
    *
    * @return Address type.
    */
   resolveType: function(address) {
     if (address.match(this.REGEXP_EMAIL)) {
       return "email";
     }
 
-    if (address.match(this.REGEXP_IPV4)) {
+    if (address.match(this.REGEXP_ENCODE_IPV4)) {
       return "IPv4";
     }
 
-    if (address.match(this.REGEXP_IPV6)) {
+    if (address.match(this.REGEXP_ENCODE_IPV6)) {
       return "IPv6";
     }
 
     let normalizedAddress = PhoneNumberUtils.normalize(address, false);
     if (PhoneNumberUtils.isPlainPhoneNumber(normalizedAddress)) {
       return "PLMN";
     }
 
     return "Others";
   },
 };
 
 defineLazyRegExp(Address, "REGEXP_DECODE_PLMN",        "^(\\+?[\\d.-]+)\\/TYPE=(PLMN)$");
-defineLazyRegExp(Address, "REGEXP_DECODE_IPV4",        "^(\\d{1,3}(?:\\.\\d{1,3}){3})\\/TYPE=(IPv4)$");
-defineLazyRegExp(Address, "REGEXP_DECODE_IPV6",        "^([\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7})\\/TYPE=(IPv6)$");
+defineLazyRegExp(Address, "REGEXP_DECODE_IPV4",        "^((?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9]))\\/TYPE=(IPv4)$");
+defineLazyRegExp(Address, "REGEXP_DECODE_IPV6",        "^(" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,7}:|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|" +
+                                                       "[0-9a-fA-F]{1,4}:(?:(?::[0-9a-fA-F]{1,4}){1,6})|" +
+                                                       ":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,4}:(?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])" +
+                                                       ")\\/TYPE=(IPv6)$");
 defineLazyRegExp(Address, "REGEXP_DECODE_CUSTOM",      "^([\\w\\+\\-.%]+)\\/TYPE=(\\w+)$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_PLMN",        "^\\+?[\\d.-]+$");
-defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4",        "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
-defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6",        "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
+defineLazyRegExp(Address, "REGEXP_ENCODE_IPV4",        "^(?:(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])\\.){3,3}(?:25[0-5]|(?:2[0-4]|1[0-9]|[1-9]){0,1}[0-9])$");
+defineLazyRegExp(Address, "REGEXP_ENCODE_IPV6",        "^(?:" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,7}:|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,6}:[0-9a-fA-F]{1,4}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,5}(?::[0-9a-fA-F]{1,4}){1,2}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,4}(?::[0-9a-fA-F]{1,4}){1,3}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,3}(?::[0-9a-fA-F]{1,4}){1,4}|" +
+                                                       "(?:[0-9a-fA-F]{1,4}:){1,2}(?::[0-9a-fA-F]{1,4}){1,5}|" +
+                                                       "[0-9a-fA-F]{1,4}:(?::[0-9a-fA-F]{1,4}){1,6}|" +
+                                                       ":(?:(?::[0-9a-fA-F]{1,4}){1,7}|:)" +
+                                                       ")$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_TYPE", "^\\w+$");
 defineLazyRegExp(Address, "REGEXP_ENCODE_CUSTOM_ADDR", "^[\\w\\+\\-.%]+$");
-defineLazyRegExp(Address, "REGEXP_NUM",                "^[\\+*#]\\d+$");
+defineLazyRegExp(Address, "REGEXP_NUM",                "^[\\+*#]?\\d+$");
 defineLazyRegExp(Address, "REGEXP_ALPHANUM",           "^\\w+$");
-defineLazyRegExp(Address, "REGEXP_PLMN",               "^\\?[\\d.-]$");
-defineLazyRegExp(Address, "REGEXP_IPV4",               "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
-defineLazyRegExp(Address, "REGEXP_IPV6",               "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
-defineLazyRegExp(Address, "REGEXP_EMAIL",              "@");
+// OMA-TS-MMS_ENC-V1_3-20110913-A chapter 8:
+//
+//   E-mail should match the definition of `mailbox` as described in section
+//   3.4 of RFC2822, but excluding the obsolete definitions as indicated by
+//   the "obs-" prefix.
+//
+// Here we try to match addr-spec only.
+defineLazyRegExp(Address, "REGEXP_EMAIL",              "(?:" +
+                                                       "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|" +
+                                                       "\"(?:[\x01-\x08\x0b\x0c\x0e-\x1f\x21\x23-\x5b\x5d-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f])*\"" +
+                                                       ")@(?:" +
+                                                       "[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+(?:\\.[a-zA-Z0-9!#$%&'*+/=?^_`{|}~-]+)*|" +
+                                                       "\\[(?:" +
+                                                       "[\x01-\x08\x0b\x0c\x0e-\x1f\x21-\x5a\x53-\x7f]|\\\\[\x01-\x09\x0b\x0c\x0e-\x7f]" +
+                                                       ")*\\]" +
+                                                       ")");
 
 /**
  * Header-field = MMS-header | Application-header
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.2
  */
 this.HeaderField = {
   /**
--- a/dom/mobilemessage/src/gonk/MmsService.js
+++ b/dom/mobilemessage/src/gonk/MmsService.js
@@ -2002,34 +2002,37 @@ MmsService.prototype = {
   createSavableFromParams: function(aMmsConnection, aParams, aMessage) {
     if (DEBUG) debug("createSavableFromParams: aParams: " + JSON.stringify(aParams));
 
     let isAddrValid = true;
     let smil = aParams.smil;
 
     // |aMessage.headers|
     let headers = aMessage["headers"] = {};
+
     let receivers = aParams.receivers;
+    let headersTo = headers["to"] = [];
     if (receivers.length != 0) {
-      let headersTo = headers["to"] = [];
       for (let i = 0; i < receivers.length; i++) {
         let receiver = receivers[i];
         let type = MMS.Address.resolveType(receiver);
         let address;
         if (type == "PLMN") {
           address = PhoneNumberUtils.normalize(receiver, false);
           if (!PhoneNumberUtils.isPlainPhoneNumber(address)) {
             isAddrValid = false;
           }
           if (DEBUG) debug("createSavableFromParams: normalize phone number " +
                            "from " + receiver + " to " + address);
         } else {
           address = receiver;
-          isAddrValid = false;
-          if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
+          if (type == "Others") {
+            isAddrValid = false;
+            if (DEBUG) debug("Error! Address is invalid to send MMS: " + address);
+          }
         }
         headersTo.push({"address": address, "type": type});
       }
     }
     if (aParams.subject) {
       headers["subject"] = aParams.subject;
     }
 
--- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
+++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm
@@ -17,18 +17,18 @@ Cu.import("resource://gre/modules/ril_co
 const RIL_GETMESSAGESCURSOR_CID =
   Components.ID("{484d1ad8-840e-4782-9dc4-9ebc4d914937}");
 const RIL_GETTHREADSCURSOR_CID =
   Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
 
 const DEBUG = false;
 const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
 
-
-const DB_VERSION = 22;
+const DB_VERSION = 23;
+
 const MESSAGE_STORE_NAME = "sms";
 const THREAD_STORE_NAME = "thread";
 const PARTICIPANT_STORE_NAME = "participant";
 const MOST_RECENT_STORE_NAME = "most-recent";
 const SMS_SEGMENT_STORE_NAME = "sms-segment";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
@@ -137,18 +137,22 @@ MobileMessageDB.prototype = {
         debug("Correct new database version:", event.newVersion == self.dbVersion);
       }
 
       let db = event.target.result;
 
       let currentVersion = event.oldVersion;
 
       function update(currentVersion) {
+        if (currentVersion >= self.dbVersion) {
+          if (DEBUG) debug("Upgrade finished.");
+          return;
+        }
+
         let next = update.bind(self, currentVersion + 1);
-
         switch (currentVersion) {
           case 0:
             if (DEBUG) debug("New database");
             self.createSchema(db, next);
             break;
           case 1:
             if (DEBUG) debug("Upgrade to version 2. Including `read` index");
             self.upgradeSchema(event.target.transaction, next);
@@ -240,18 +244,18 @@ MobileMessageDB.prototype = {
             if (DEBUG) debug("Upgrade to version 21. Add sentTimestamp.");
             self.upgradeSchema20(event.target.transaction, next);
             break;
           case 21:
             if (DEBUG) debug("Upgrade to version 22. Add sms-segment store.");
             self.upgradeSchema21(db, event.target.transaction, next);
             break;
           case 22:
-            // This will need to be moved for each new version
-            if (DEBUG) debug("Upgrade finished.");
+            if (DEBUG) debug("Upgrade to version 23. Add type information to receivers and to");
+            self.upgradeSchema22(event.target.transaction, next);
             break;
           default:
             event.target.transaction.abort();
             if (DEBUG) debug("unexpected db version: " + event.oldVersion);
             callback(Cr.NS_ERROR_FAILURE, null);
             break;
         }
       }
@@ -696,18 +700,18 @@ MobileMessageDB.prototype = {
       }
 
       let mostRecentRecord = mostRecentCursor.value;
 
       // Each entry in mostRecentStore is supposed to be a unique thread, so we
       // retrieve the records out and insert its "senderOrReceiver" column as a
       // new record in participantStore.
       let number = mostRecentRecord.senderOrReceiver;
-      self.findParticipantRecordByAddress(participantStore, number, true,
-                                          function(participantRecord) {
+      self.findParticipantRecordByPlmnAddress(participantStore, number, true,
+                                              function(participantRecord) {
         // Also create a new record in threadStore.
         let threadRecord = {
           participantIds: [participantRecord.id],
           participantAddresses: [number],
           lastMessageId: mostRecentRecord.id,
           lastTimestamp: mostRecentRecord.timestamp,
           subject: mostRecentRecord.body,
           unreadCount: mostRecentRecord.unreadCount,
@@ -1067,19 +1071,19 @@ MobileMessageDB.prototype = {
             else if (messageRecord.delivery === DELIVERY_SENT ||
                 messageRecord.delivery === DELIVERY_ERROR) {
               if (messageRecord.type == "sms") {
                 threadParticipants = [messageRecord.receiver];
               } else if (messageRecord.type == "mms") {
                 threadParticipants = messageRecord.receivers;
               }
             }
-            self.findThreadRecordByParticipants(threadStore, participantStore,
-                                                threadParticipants, true,
-                                                function(threadRecord,
+            self.findThreadRecordByPlmnAddresses(threadStore, participantStore,
+                                                 threadParticipants, true,
+                                                 function(threadRecord,
                                                           participantIds) {
               if (!participantIds) {
                 debug("participantIds is empty!");
                 return;
               }
 
               let timestamp = messageRecord.timestamp;
               // Setup participantIdsIndex.
@@ -1403,16 +1407,219 @@ MobileMessageDB.prototype = {
      */
     let smsSegmentStore = db.createObjectStore(SMS_SEGMENT_STORE_NAME,
                                                { keyPath: "id",
                                                  autoIncrement: true });
     smsSegmentStore.createIndex("hash", "hash", { unique: true });
     next();
   },
 
+  /**
+   * Change receivers format to address and type.
+   */
+  upgradeSchema22: function(transaction, next) {
+    // Since bug 871433 (DB_VERSION 11), we normalize addresses before really
+    // diving into participant store in findParticipantRecordByPlmnAddress.
+    // This also follows that all addresses stored in participant store are
+    // normalized phone numbers, although they might not be phone numbers at the
+    // first place.  So addresses in participant store are not reliable.
+    //
+    // |participantAddresses| in a thread record are reliable, but several
+    // distinct threads can be wrongly mapped into one.  For example, an IPv4
+    // address "55.252.255.54" was normalized as US phone number "5525225554".
+    // So beginning with thread store is not really a good idea.
+    //
+    // The only correct way is to begin with all messages records and check if
+    // the findThreadRecordByTypedAddresses() call using a message record's
+    // thread participants returns the same thread record with the one it
+    // currently belong to.
+
+    function getThreadParticipantsFromMessageRecord(aMessageRecord) {
+      let threadParticipants;
+
+      if (aMessageRecord.type == "sms") {
+        let address;
+        if (aMessageRecord.delivery == DELIVERY_RECEIVED) {
+          address = aMessageRecord.sender;
+        } else {
+          address = aMessageRecord.receiver;
+        }
+        threadParticipants = [{
+          address: address,
+          type: MMS.Address.resolveType(address)
+        }];
+      } else { // MMS
+        if ((aMessageRecord.delivery == DELIVERY_RECEIVED) ||
+            (aMessageRecord.delivery == DELIVERY_NOT_DOWNLOADED)) {
+          // DISABLE_MMS_GROUPING_FOR_RECEIVING is set to true at the time, so
+          // we consider only |aMessageRecord.sender|.
+          threadParticipants = [{
+            address: aMessageRecord.sender,
+            type: MMS.Address.resolveType(aMessageRecord.sender)
+          }];
+        } else {
+          threadParticipants = aMessageRecord.headers.to;
+        }
+      }
+
+      return threadParticipants;
+    }
+
+    let participantStore = transaction.objectStore(PARTICIPANT_STORE_NAME);
+    let threadStore = transaction.objectStore(THREAD_STORE_NAME);
+    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
+
+    let invalidThreadIds = [];
+
+    let self = this;
+    let messageCursorReq = messageStore.openCursor();
+    messageCursorReq.onsuccess = function(aEvent) {
+      let messageCursor = aEvent.target.result;
+      if (messageCursor) {
+        let messageRecord = messageCursor.value;
+        let threadParticipants =
+          getThreadParticipantsFromMessageRecord(messageRecord);
+
+        // 1. If thread ID of this message record has been marked as invalid,
+        //    skip further checks and go ahead for the next one.
+        if (invalidThreadIds.indexOf(messageRecord.threadId) >= 0) {
+          messageCursor.continue();
+          return;
+        }
+
+        // 2. Check if the thread record found with the new algorithm matches
+        //    the original one.
+        self.findThreadRecordByTypedAddresses(threadStore, participantStore,
+                                              threadParticipants, true,
+                                              function(aThreadRecord,
+                                                       aParticipantIds) {
+          if (!aThreadRecord || aThreadRecord.id !== messageRecord.threadId) {
+            invalidThreadIds.push(messageRecord.threadId);
+          }
+
+          messageCursor.continue();
+        });
+
+        // Only calls |messageCursor.continue()| inside the callback of
+        // findThreadRecordByTypedAddresses() because that may inserts new
+        // participant records and hurt concurrency.
+        return;
+      } // End of |if (messageCursor)|.
+
+      // 3. If there is no any mis-grouped message found, go on to next upgrade.
+      if (!invalidThreadIds.length) {
+        next();
+        return;
+      }
+
+      // 4. Remove invalid thread records first, so that we don't have
+      //    unexpected match in findThreadRecordByTypedAddresses().
+      invalidThreadIds.forEach(function(aInvalidThreadId) {
+        threadStore.delete(aInvalidThreadId);
+      });
+
+      // 5. For each affected thread, re-create a valid thread record for it.
+      (function redoThreading(aInvalidThreadId) {
+        // 5-1. For each message record originally belongs to this thread, find
+        //      a new home for it.
+        let range = IDBKeyRange.bound([aInvalidThreadId, 0],
+                                      [aInvalidThreadId, ""]);
+        let threadMessageCursorReq = messageStore.index("threadId")
+                                                 .openCursor(range, NEXT);
+        threadMessageCursorReq.onsuccess = function(aEvent) {
+          let messageCursor = aEvent.target.result;
+
+          // 5-2. If no more message records to process in this invalid thread,
+          //      go on to next invalid thread if available, or pass to next
+          //      upgradeSchema function.
+          if (!messageCursor) {
+            if (invalidThreadIds.length) {
+              redoThreading(invalidThreadIds.shift());
+            } else {
+              next();
+            }
+            return;
+          }
+
+          let messageRecord = messageCursor.value;
+          let threadParticipants =
+            getThreadParticipantsFromMessageRecord(messageRecord);
+
+          // 5-3. Assign a thread record for this message record. Basically
+          //      copied from |realSaveRecord|, but we don't have to worry
+          //      about |updateThreadByMessageChange| because we've removed
+          //      affected threads.
+          self.findThreadRecordByTypedAddresses(threadStore, participantStore,
+                                                threadParticipants, true,
+                                                function(aThreadRecord,
+                                                         aParticipantIds) {
+            // Setup participantIdsIndex.
+            messageRecord.participantIdsIndex =
+              aParticipantIds.map(function(aParticipantId) {
+                return [aParticipantId, messageRecord.timestamp];
+              });
+
+            let threadExists = aThreadRecord ? true : false;
+            if (!threadExists) {
+              aThreadRecord = {
+                participantIds: aParticipantIds,
+                participantAddresses:
+                  threadParticipants.map(function(aTypedAddress) {
+                    return aTypedAddress.address;
+                  }),
+                unreadCount: 0,
+                lastTimestamp: -1
+              };
+            }
+
+            let needsUpdate = false;
+            if (aThreadRecord.lastTimestamp <= messageRecord.timestamp) {
+              let lastMessageSubject;
+              if (messageRecord.type == "mms") {
+                lastMessageSubject = messageRecord.headers.subject;
+              }
+              aThreadRecord.lastMessageSubject = lastMessageSubject || null;
+              aThreadRecord.lastTimestamp = messageRecord.timestamp;
+              aThreadRecord.body = messageRecord.body;
+              aThreadRecord.lastMessageId = messageRecord.id;
+              aThreadRecord.lastMessageType = messageRecord.type;
+              needsUpdate = true;
+            }
+
+            if (!messageRecord.read) {
+              aThreadRecord.unreadCount++;
+              needsUpdate = true;
+            }
+
+            let updateMessageRecordThreadId = function(aThreadId) {
+              // Setup threadId & threadIdIndex.
+              messageRecord.threadId = aThreadId;
+              messageRecord.threadIdIndex = [aThreadId, messageRecord.timestamp];
+
+              messageCursor.update(messageRecord);
+              messageCursor.continue();
+            };
+
+            if (threadExists) {
+              if (needsUpdate) {
+                threadStore.put(aThreadRecord);
+              }
+              updateMessageRecordThreadId(aThreadRecord.id);
+            } else {
+              threadStore.add(aThreadRecord).onsuccess = function(aEvent) {
+                let threadId = aEvent.target.result;
+                updateMessageRecordThreadId(threadId);
+              };
+            }
+          }); // End of findThreadRecordByTypedAddresses().
+        }; // End of threadMessageCursorReq.onsuccess.
+      })(invalidThreadIds.shift()); // End of function redoThreading.
+    }; // End of messageStore.openCursor().onsuccess
+  },
+
   matchParsedPhoneNumbers: function(addr1, parsedAddr1, addr2, parsedAddr2) {
     if ((parsedAddr1.internationalNumber &&
          parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) ||
         (parsedAddr1.nationalNumber &&
          parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) {
       return true;
     }
 
@@ -1531,20 +1738,32 @@ MobileMessageDB.prototype = {
                                                     subject,
                                                     smil,
                                                     attachments,
                                                     expiryDate,
                                                     readReportRequested);
     }
   },
 
-  findParticipantRecordByAddress: function(aParticipantStore, aAddress,
-                                           aCreate, aCallback) {
+  createParticipantRecord: function(aParticipantStore, aAddresses, aCallback) {
+    let participantRecord = { addresses: aAddresses };
+    let addRequest = aParticipantStore.add(participantRecord);
+    addRequest.onsuccess = function(event) {
+      participantRecord.id = event.target.result;
+      if (DEBUG) {
+        debug("createParticipantRecord: " + JSON.stringify(participantRecord));
+      }
+      aCallback(participantRecord);
+    };
+  },
+
+  findParticipantRecordByPlmnAddress: function(aParticipantStore, aAddress,
+                                               aCreate, aCallback) {
     if (DEBUG) {
-      debug("findParticipantRecordByAddress("
+      debug("findParticipantRecordByPlmnAddress("
             + JSON.stringify(aAddress) + ", " + aCreate + ")");
     }
 
     // Two types of input number to match here, international(+886987654321),
     // and local(0987654321) types. The "nationalNumber" parsed from
     // phonenumberutils will be "987654321" in this case.
 
     // Normalize address before searching for participant record.
@@ -1554,30 +1773,30 @@ MobileMessageDB.prototype = {
     if (parsedAddress && parsedAddress.internationalNumber &&
         allPossibleAddresses.indexOf(parsedAddress.internationalNumber) < 0) {
       // We only stores international numbers into participant store because
       // the parsed national number doesn't contain country info and may
       // duplicate in different country.
       allPossibleAddresses.push(parsedAddress.internationalNumber);
     }
     if (DEBUG) {
-      debug("findParticipantRecordByAddress: allPossibleAddresses = " +
+      debug("findParticipantRecordByPlmnAddress: allPossibleAddresses = " +
             JSON.stringify(allPossibleAddresses));
     }
 
     // Make a copy here because we may need allPossibleAddresses again.
     let needles = allPossibleAddresses.slice(0);
     let request = aParticipantStore.index("addresses").get(needles.pop());
     request.onsuccess = (function onsuccess(event) {
       let participantRecord = event.target.result;
       // 1) First try matching through "addresses" index of participant store.
       //    If we're lucky, return the fetched participant record.
       if (participantRecord) {
         if (DEBUG) {
-          debug("findParticipantRecordByAddress: got "
+          debug("findParticipantRecordByPlmnAddress: got "
                 + JSON.stringify(participantRecord));
         }
         aCallback(participantRecord);
         return;
       }
 
       // Try next possible address again.
       if (needles.length) {
@@ -1591,26 +1810,18 @@ MobileMessageDB.prototype = {
         let cursor = event.target.result;
         if (!cursor) {
           // Have traversed whole object store but still in vain.
           if (!aCreate) {
             aCallback(null);
             return;
           }
 
-          let participantRecord = { addresses: [normalizedAddress] };
-          let addRequest = aParticipantStore.add(participantRecord);
-          addRequest.onsuccess = function(event) {
-            participantRecord.id = event.target.result;
-            if (DEBUG) {
-              debug("findParticipantRecordByAddress: created "
-                    + JSON.stringify(participantRecord));
-            }
-            aCallback(participantRecord);
-          };
+          this.createParticipantRecord(aParticipantStore, [normalizedAddress],
+                                       aCallback);
           return;
         }
 
         let participantRecord = cursor.value;
         for (let storedAddress of participantRecord.addresses) {
           let parsedStoredAddress = PhoneNumberUtils.parseWithMCC(storedAddress, null);
           let match = this.matchPhoneNumbers(normalizedAddress, parsedAddress,
                                              storedAddress, parsedStoredAddress);
@@ -1623,100 +1834,220 @@ MobileMessageDB.prototype = {
             // In a READ-WRITE transaction, append one more possible address for
             // this participant record.
             participantRecord.addresses =
               participantRecord.addresses.concat(allPossibleAddresses);
             cursor.update(participantRecord);
           }
 
           if (DEBUG) {
-            debug("findParticipantRecordByAddress: match "
+            debug("findParticipantRecordByPlmnAddress: match "
                   + JSON.stringify(cursor.value));
           }
           aCallback(participantRecord);
           return;
         }
 
         // Check next participant record if available.
         cursor.continue();
       }).bind(this);
     }).bind(this);
   },
 
-  findParticipantIdsByAddresses: function(aParticipantStore, aAddresses,
-                                          aCreate, aSkipNonexistent, aCallback) {
+  findParticipantRecordByOtherAddress: function(aParticipantStore, aAddress,
+                                                aCreate, aCallback) {
     if (DEBUG) {
-      debug("findParticipantIdsByAddresses("
+      debug("findParticipantRecordByOtherAddress(" +
+            JSON.stringify(aAddress) + ", " + aCreate + ")");
+    }
+
+    // Go full match.
+    let request = aParticipantStore.index("addresses").get(aAddress);
+    request.onsuccess = (function(event) {
+      let participantRecord = event.target.result;
+      if (participantRecord) {
+        if (DEBUG) {
+          debug("findParticipantRecordByOtherAddress: got "
+                + JSON.stringify(participantRecord));
+        }
+        aCallback(participantRecord);
+        return;
+      }
+      if (aCreate) {
+        this.createParticipantRecord(aParticipantStore, [aAddress], aCallback);
+        return;
+      }
+      aCallback(null);
+    }).bind(this);
+  },
+
+  findParticipantRecordByTypedAddress: function(aParticipantStore,
+                                                aTypedAddress, aCreate,
+                                                aCallback) {
+    if (aTypedAddress.type == "PLMN") {
+      this.findParticipantRecordByPlmnAddress(aParticipantStore,
+                                              aTypedAddress.address, aCreate,
+                                              aCallback);
+    } else {
+      this.findParticipantRecordByOtherAddress(aParticipantStore,
+                                               aTypedAddress.address, aCreate,
+                                               aCallback);
+    }
+  },
+
+  // For upgradeSchema13 usage.
+  findParticipantIdsByPlmnAddresses: function(aParticipantStore, aAddresses,
+                                              aCreate, aSkipNonexistent, aCallback) {
+    if (DEBUG) {
+      debug("findParticipantIdsByPlmnAddresses("
             + JSON.stringify(aAddresses) + ", "
             + aCreate + ", " + aSkipNonexistent + ")");
     }
 
     if (!aAddresses || !aAddresses.length) {
-      if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
+      if (DEBUG) debug("findParticipantIdsByPlmnAddresses: returning null");
       aCallback(null);
       return;
     }
 
     let self = this;
     (function findParticipantId(index, result) {
       if (index >= aAddresses.length) {
         // Sort numerically.
         result.sort(function(a, b) {
           return a - b;
         });
-        if (DEBUG) debug("findParticipantIdsByAddresses: returning " + result);
+        if (DEBUG) debug("findParticipantIdsByPlmnAddresses: returning " + result);
         aCallback(result);
         return;
       }
 
-      self.findParticipantRecordByAddress(aParticipantStore,
-                                          aAddresses[index++], aCreate,
-                                          function(participantRecord) {
+      self.findParticipantRecordByPlmnAddress(aParticipantStore,
+                                              aAddresses[index++], aCreate,
+                                              function(participantRecord) {
         if (!participantRecord) {
           if (!aSkipNonexistent) {
-            if (DEBUG) debug("findParticipantIdsByAddresses: returning null");
+            if (DEBUG) debug("findParticipantIdsByPlmnAddresses: returning null");
             aCallback(null);
             return;
           }
         } else if (result.indexOf(participantRecord.id) < 0) {
           result.push(participantRecord.id);
         }
         findParticipantId(index, result);
       });
     }) (0, []);
   },
 
-  findThreadRecordByParticipants: function(aThreadStore, aParticipantStore,
-                                           aAddresses, aCreateParticipants,
-                                           aCallback) {
+  findParticipantIdsByTypedAddresses: function(aParticipantStore,
+                                               aTypedAddresses, aCreate,
+                                               aSkipNonexistent, aCallback) {
     if (DEBUG) {
-      debug("findThreadRecordByParticipants(" + JSON.stringify(aAddresses)
+      debug("findParticipantIdsByTypedAddresses(" +
+            JSON.stringify(aTypedAddresses) + ", " +
+            aCreate + ", " + aSkipNonexistent + ")");
+    }
+
+    if (!aTypedAddresses || !aTypedAddresses.length) {
+      if (DEBUG) debug("findParticipantIdsByTypedAddresses: returning null");
+      aCallback(null);
+      return;
+    }
+
+    let self = this;
+    (function findParticipantId(index, result) {
+      if (index >= aTypedAddresses.length) {
+        // Sort numerically.
+        result.sort(function(a, b) {
+          return a - b;
+        });
+        if (DEBUG) {
+          debug("findParticipantIdsByTypedAddresses: returning " + result);
+        }
+        aCallback(result);
+        return;
+      }
+
+      self.findParticipantRecordByTypedAddress(aParticipantStore,
+                                               aTypedAddresses[index++],
+                                               aCreate,
+                                               function(participantRecord) {
+        if (!participantRecord) {
+          if (!aSkipNonexistent) {
+            if (DEBUG) {
+              debug("findParticipantIdsByTypedAddresses: returning null");
+            }
+            aCallback(null);
+            return;
+          }
+        } else if (result.indexOf(participantRecord.id) < 0) {
+          result.push(participantRecord.id);
+        }
+        findParticipantId(index, result);
+      });
+    }) (0, []);
+  },
+
+  // For upgradeSchema13 usage.
+  findThreadRecordByPlmnAddresses: function(aThreadStore, aParticipantStore,
+                                            aAddresses, aCreateParticipants,
+                                            aCallback) {
+    if (DEBUG) {
+      debug("findThreadRecordByPlmnAddresses(" + JSON.stringify(aAddresses)
             + ", " + aCreateParticipants + ")");
     }
-    this.findParticipantIdsByAddresses(aParticipantStore, aAddresses,
-                                       aCreateParticipants, false,
-                                       function(participantIds) {
+    this.findParticipantIdsByPlmnAddresses(aParticipantStore, aAddresses,
+                                           aCreateParticipants, false,
+                                           function(participantIds) {
       if (!participantIds) {
-        if (DEBUG) debug("findThreadRecordByParticipants: returning null");
+        if (DEBUG) debug("findThreadRecordByPlmnAddresses: returning null");
         aCallback(null, null);
         return;
       }
       // Find record from thread store.
       let request = aThreadStore.index("participantIds").get(participantIds);
       request.onsuccess = function(event) {
         let threadRecord = event.target.result;
         if (DEBUG) {
-          debug("findThreadRecordByParticipants: return "
+          debug("findThreadRecordByPlmnAddresses: return "
                 + JSON.stringify(threadRecord));
         }
         aCallback(threadRecord, participantIds);
       };
     });
   },
 
+  findThreadRecordByTypedAddresses: function(aThreadStore, aParticipantStore,
+                                             aTypedAddresses,
+                                             aCreateParticipants, aCallback) {
+    if (DEBUG) {
+      debug("findThreadRecordByTypedAddresses(" +
+          JSON.stringify(aTypedAddresses) + ", " + aCreateParticipants + ")");
+    }
+    this.findParticipantIdsByTypedAddresses(aParticipantStore, aTypedAddresses,
+                                            aCreateParticipants, false,
+                                            function(participantIds) {
+      if (!participantIds) {
+        if (DEBUG) debug("findThreadRecordByTypedAddresses: returning null");
+        aCallback(null, null);
+        return;
+      }
+      // Find record from thread store.
+      let request = aThreadStore.index("participantIds").get(participantIds);
+      request.onsuccess = function(event) {
+        let threadRecord = event.target.result;
+        if (DEBUG) {
+          debug("findThreadRecordByTypedAddresses: return " +
+                JSON.stringify(threadRecord));
+        }
+        aCallback(threadRecord, participantIds);
+      };
+    });
+  },
+
   newTxnWithCallback: function(aCallback, aFunc, aStoreNames) {
     let self = this;
     this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) {
       let notifyResult = function(aRv, aMessageRecord) {
         if (!aCallback) {
           return;
         }
         let domMessage =
@@ -1737,17 +2068,17 @@ MobileMessageDB.prototype = {
         // TODO bug 832140 check event.target.errorCode
         notifyResult(Cr.NS_ERROR_FAILURE, null);
       };
 
       aFunc(capture, aStores);
     }, aStoreNames);
   },
 
-  saveRecord: function(aMessageRecord, aAddresses, aCallback) {
+  saveRecord: function(aMessageRecord, aThreadParticipants, aCallback) {
     if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
 
     let self = this;
     this.newTxn(READ_WRITE, function(error, txn, stores) {
       let notifyResult = function(aRv, aMessageRecord) {
         if (!aCallback) {
           return;
         }
@@ -1771,89 +2102,95 @@ MobileMessageDB.prototype = {
         // TODO bug 832140 check event.target.errorCode
         notifyResult(Cr.NS_ERROR_FAILURE, null);
       };
 
       let messageStore = stores[0];
       let participantStore = stores[1];
       let threadStore = stores[2];
       self.replaceShortMessageOnSave(txn, messageStore, participantStore,
-                                     threadStore, aMessageRecord, aAddresses);
+                                     threadStore, aMessageRecord,
+                                     aThreadParticipants);
     }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
   },
 
   replaceShortMessageOnSave: function(aTransaction, aMessageStore,
                                       aParticipantStore, aThreadStore,
-                                      aMessageRecord, aAddresses) {
+                                      aMessageRecord, aThreadParticipants) {
     let isReplaceTypePid = (aMessageRecord.pid) &&
                            ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
                              aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
                             aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE);
 
     if (aMessageRecord.type != "sms" ||
         aMessageRecord.delivery != DELIVERY_RECEIVED ||
         !isReplaceTypePid) {
       this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                          aThreadStore, aMessageRecord, aAddresses);
+                          aThreadStore, aMessageRecord, aThreadParticipants);
       return;
     }
 
     // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)":
     //
     //   ... the MS shall check the originating address and replace any
     //   existing stored message having the same Protocol Identifier code
     //   and originating address with the new short message and other
     //   parameter values. If there is no message to be replaced, the MS
     //   shall store the message in the normal way. ... it is recommended
     //   that the SC address should not be checked by the MS."
     let self = this;
-    this.findParticipantRecordByAddress(aParticipantStore,
-                                        aMessageRecord.sender, false,
-                                        function(participantRecord) {
+    let typedSender = {
+      address: aMessageRecord.sender,
+      type: MMS.Address.resolveType(aMessageRecord.sender)
+    };
+    this.findParticipantRecordByTypedAddress(aParticipantStore, typedSender,
+                                             false,
+                                             function(participantRecord) {
       if (!participantRecord) {
         self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                            aThreadStore, aMessageRecord, aAddresses);
+                            aThreadStore, aMessageRecord, aThreadParticipants);
         return;
       }
 
       let participantId = participantRecord.id;
       let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]);
       let request = aMessageStore.index("participantIds").openCursor(range);
       request.onsuccess = function onsuccess(event) {
         let cursor = event.target.result;
         if (!cursor) {
           self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                              aThreadStore, aMessageRecord, aAddresses);
+                              aThreadStore, aMessageRecord, aThreadParticipants);
           return;
         }
 
         // A message record with same participantId found.
         // Verify matching criteria.
         let foundMessageRecord = cursor.value;
         if (foundMessageRecord.type != "sms" ||
             foundMessageRecord.sender != aMessageRecord.sender ||
             foundMessageRecord.pid != aMessageRecord.pid) {
           cursor.continue();
           return;
         }
 
         // Match! Now replace that found message record with current one.
         aMessageRecord.id = foundMessageRecord.id;
         self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
-                            aThreadStore, aMessageRecord, aAddresses);
+                            aThreadStore, aMessageRecord, aThreadParticipants);
       };
     });
   },
 
   realSaveRecord: function(aTransaction, aMessageStore, aParticipantStore,
-                           aThreadStore, aMessageRecord, aAddresses) {
+                           aThreadStore, aMessageRecord, aThreadParticipants) {
     let self = this;
-    this.findThreadRecordByParticipants(aThreadStore, aParticipantStore,
-                                        aAddresses, true,
-                                        function(threadRecord, participantIds) {
+    this.findThreadRecordByTypedAddresses(aThreadStore, aParticipantStore,
+                                          aThreadParticipants, true,
+                                          function(threadRecord,
+                                                   participantIds) {
       if (!participantIds) {
         aTransaction.abort();
         return;
       }
 
       let isOverriding = (aMessageRecord.id !== undefined);
       if (!isOverriding) {
         // |self.lastMessageId| is only updated in |txn.oncomplete|.
@@ -1925,17 +2262,19 @@ MobileMessageDB.prototype = {
 
       let lastMessageSubject;
       if (aMessageRecord.type == "mms") {
         lastMessageSubject = aMessageRecord.headers.subject;
       }
 
       threadRecord = {
         participantIds: participantIds,
-        participantAddresses: aAddresses,
+        participantAddresses: aThreadParticipants.map(function(typedAddress) {
+          return typedAddress.address;
+        }),
         lastMessageId: aMessageRecord.id,
         lastTimestamp: timestamp,
         lastMessageSubject: lastMessageSubject || null,
         body: aMessageRecord.body,
         unreadCount: aMessageRecord.read ? 0 : 1,
         lastMessageType: aMessageRecord.type,
       };
       aThreadStore.add(threadRecord).onsuccess = function(event) {
@@ -2122,17 +2461,23 @@ MobileMessageDB.prototype = {
 
     if (!isSuccess) {
       // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's
       // own phone number), so we cannot correcly exclude the user's own
       // number from the receivers, thus wrongly building the thread index.
       if (DEBUG) debug("Error! Cannot strip out user's own phone number!");
     }
 
-    threadParticipants = threadParticipants.concat(slicedReceivers);
+    threadParticipants =
+      threadParticipants.concat(slicedReceivers).map(function(aAddress) {
+        return {
+          address: aAddress,
+          type: MMS.Address.resolveType(aAddress)
+        };
+      });
   },
 
   updateThreadByMessageChange: function(messageStore, threadStore, threadId,
                                         messageId, messageRead) {
     threadStore.get(threadId).onsuccess = function(event) {
       // This must exist.
       let threadRecord = event.target.result;
       if (DEBUG) debug("Updating thread record " + JSON.stringify(threadRecord));
@@ -2202,23 +2547,29 @@ MobileMessageDB.prototype = {
       return;
     }
 
     let threadParticipants;
     if (aMessage.type == "mms") {
       if (aMessage.headers.from) {
         aMessage.sender = aMessage.headers.from.address;
       } else {
-        aMessage.sender = "anonymous";
+        aMessage.sender = "";
       }
 
-      threadParticipants = [aMessage.sender];
+      threadParticipants = [{
+        address: aMessage.sender,
+        type: MMS.Address.resolveType(aMessage.sender)
+      }];
       this.fillReceivedMmsThreadParticipants(aMessage, threadParticipants);
     } else { // SMS
-      threadParticipants = [aMessage.sender];
+      threadParticipants = [{
+        address: aMessage.sender,
+        type: MMS.Address.resolveType(aMessage.sender)
+      }];
     }
 
     let timestamp = aMessage.timestamp;
 
     // Adding needed indexes and extra attributes for internal use.
     // threadIdIndex & participantIdsIndex are filled in saveRecord().
     aMessage.readIndex = [FILTER_READ_UNREAD, timestamp];
     aMessage.read = FILTER_READ_UNREAD;
@@ -2279,26 +2630,17 @@ MobileMessageDB.prototype = {
                        : DELIVERY_STATUS_NOT_APPLICABLE;
     if (aMessage.type == "sms") {
       aMessage.deliveryStatus = deliveryStatus;
       // If |deliveryTimestamp| is not specified, use 0 as default.
       if (aMessage.deliveryTimestamp == undefined) {
         aMessage.deliveryTimestamp = 0;
       }
     } else if (aMessage.type == "mms") {
-      let receivers = aMessage.receivers
-      if (!Array.isArray(receivers)) {
-        if (DEBUG) {
-          debug("Need receivers for MMS. Fail to save the sending message.");
-        }
-        if (aCallback) {
-          aCallback.notify(Cr.NS_ERROR_FAILURE, null);
-        }
-        return;
-      }
+      let receivers = aMessage.receivers;
       let readStatus = aMessage.headers["x-mms-read-report"]
                      ? MMS.DOM_READ_STATUS_PENDING
                      : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
       aMessage.deliveryInfo = [];
       for (let i = 0; i < receivers.length; i++) {
         aMessage.deliveryInfo.push({
           receiver: receivers[i],
           deliveryStatus: deliveryStatus,
@@ -2317,23 +2659,26 @@ MobileMessageDB.prototype = {
     aMessage.readIndex = [FILTER_READ_READ, timestamp];
     aMessage.delivery = DELIVERY_SENDING;
     aMessage.messageClass = MESSAGE_CLASS_NORMAL;
     aMessage.read = FILTER_READ_READ;
 
     // |sentTimestamp| is not available when the message is still sedning.
     aMessage.sentTimestamp = 0;
 
-    let addresses;
+    let threadParticipants;
     if (aMessage.type == "sms") {
-      addresses = [aMessage.receiver];
+      threadParticipants = [{
+        address: aMessage.receiver,
+        type :MMS.Address.resolveType(aMessage.receiver)
+      }];
     } else if (aMessage.type == "mms") {
-      addresses = aMessage.receivers;
+      threadParticipants = aMessage.headers.to;
     }
-    this.saveRecord(aMessage, addresses, aCallback);
+    this.saveRecord(aMessage, threadParticipants, aCallback);
   },
 
   setMessageDeliveryByMessageId: function(messageId, receiver, delivery,
                                           deliveryStatus, envelopeId, callback) {
     this.updateMessageDeliveryById(messageId, "messageId",
                                    receiver, delivery, deliveryStatus,
                                    envelopeId, callback);
 
@@ -3006,19 +3351,25 @@ let FilterSearcherHelper = {
     if (filter.numbers) {
       if (DEBUG) debug("filter.numbers " + filter.numbers.join(", "));
 
       if (!single) {
         collect = intersectionCollector.newContext();
       }
 
       let participantStore = txn.objectStore(PARTICIPANT_STORE_NAME);
-      mmdb.findParticipantIdsByAddresses(participantStore, filter.numbers,
-                                         false, true,
-                                         (function(participantIds) {
+      let typedAddresses = filter.numbers.map(function(number) {
+        return {
+          address: number,
+          type: MMS.Address.resolveType(number)
+        };
+      });
+      mmdb.findParticipantIdsByTypedAddresses(participantStore, typedAddresses,
+                                              false, true,
+                                              (function(participantIds) {
         if (!participantIds || !participantIds.length) {
           // Oops! No such participant at all.
 
           collect(txn, COLLECT_ID_END, COLLECT_TIMESTAMP_UNUSED);
           return;
         }
 
         if (participantIds.length == 1) {
--- a/dom/mobilemessage/tests/marionette/manifest.ini
+++ b/dom/mobilemessage/tests/marionette/manifest.ini
@@ -38,12 +38,14 @@ qemu = true
 [test_getthreads.js]
 [test_smsc_address.js]
 [test_dsds_default_service_id.js]
 [test_thread_subject.js]
 [test_mmdb_new.js]
 [test_mmdb_setmessagedeliverybyid_sms.js]
 [test_mmdb_foreachmatchedmmsdeliveryinfo.js]
 [test_mmdb_full_storage.js]
+[test_mmdb_upgradeSchema_current_structure.js]
+[test_mmdb_upgradeSchema_22.js]
 [test_replace_short_message_type.js]
 [test_mt_sms_concatenation.js]
 [test_error_of_mms_manual_retrieval.js]
 [test_error_of_mms_send.js]
--- a/dom/mobilemessage/tests/marionette/test_filter_number.js
+++ b/dom/mobilemessage/tests/marionette/test_filter_number.js
@@ -1,201 +1,101 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'head.js';
 
-const NUM_THREADS = 10;
-const REMOTE_NATIONAL_NUMBER = "555531555";
+const REMOTE_NATIONAL_NUMBER = "552522555";
 const REMOTE_INTERNATIONAL_NUMBER = "+1" + REMOTE_NATIONAL_NUMBER;
 
-SpecialPowers.addPermission("sms", true, document);
-SpecialPowers.setBoolPref("dom.sms.enabled", true);
-
-let pendingEmulatorCmdCount = 0;
-function sendSmsToEmulator(from, text) {
-  ++pendingEmulatorCmdCount;
+const TEXT_1 = "Nice to meet you";
+const TEXT_2 = "Nice to meet you, too";
 
-  let cmd = "sms send " + from + " " + text;
-  runEmulatorCmd(cmd, function(result) {
-    --pendingEmulatorCmdCount;
-
-    is(result[0], "OK", "Emulator response");
-  });
+// Have a long long subject causes the send fails, so we don't need
+// networking here.
+const MMS_MAX_LENGTH_SUBJECT = 40;
+function genMmsSubject(sep) {
+  return "Hello " + (new Array(MMS_MAX_LENGTH_SUBJECT).join(sep)) + " World!";
 }
 
-let tasks = {
-  // List of test fuctions. Each of them should call |tasks.next()| when
-  // completed or |tasks.finish()| to jump to the last one.
-  _tasks: [],
-  _nextTaskIndex: 0,
-
-  push: function(func) {
-    this._tasks.push(func);
-  },
-
-  next: function() {
-    let index = this._nextTaskIndex++;
-    let task = this._tasks[index];
-    try {
-      task();
-    } catch (ex) {
-      ok(false, "test task[" + index + "] throws: " + ex);
-      // Run last task as clean up if possible.
-      if (index != this._tasks.length - 1) {
-        this.finish();
-      }
-    }
-  },
-
-  finish: function() {
-    this._tasks[this._tasks.length - 1]();
-  },
-
-  run: function() {
-    this.next();
-  }
-};
-
-let manager;
-function getAllMessages(callback, filter, reverse) {
-  if (!filter) {
-    filter = new MozSmsFilter;
-  }
-  let messages = [];
-  let request = manager.getMessages(filter, reverse || false);
-  request.onsuccess = function(event) {
-    if (request.result) {
-      messages.push(request.result);
-      request.continue();
-      return;
-    }
-
-    window.setTimeout(callback.bind(null, messages), 0);
-  }
+function genFailingMms(aReceivers) {
+  return {
+    receivers: aReceivers,
+    subject: genMmsSubject(' '),
+    attachments: [],
+  };
 }
 
-function deleteAllMessages() {
-  log("Deleting all messages.");
-  getAllMessages(function deleteAll(messages) {
-    let message = messages.shift();
-    if (!message) {
-      ok(true, "all messages deleted");
-      tasks.next();
-      return;
-    }
-
-    let request = manager.delete(message.id);
-    request.onsuccess = deleteAll.bind(null, messages);
-    request.onerror = function(event) {
-      ok(false, "failed to delete all messages");
-      tasks.finish();
-    }
-  });
-}
-
-function checkMessage(needle, secondary) {
-  log("  Verifying " + needle);
+function checkMessage(aNeedle, aValidNumbers) {
+  log("  Verifying " + aNeedle);
 
   let filter = new MozSmsFilter();
-  filter.numbers = [needle];
-  getAllMessages(function(messages) {
-    is(messages.length, 2, "should have exactly 2 messages");
+  filter.numbers = [aNeedle];
+  return getMessages(filter)
+    .then(function(messages) {
+      // Check the messages are sent to/received from aValidNumbers.
+      is(messages.length, aValidNumbers.length, "messages.length");
 
-    // Check the messages are sent to/received from either 'needle' or
-    // 'secondary' number.
-    let validNumbers = [needle, secondary];
-    for (let message of messages) {
-      let number = (message.delivery === "received") ? message.sender
+      for (let message of messages) {
+        let number;
+        if (message.type == "sms") {
+          number = (message.delivery === "received") ? message.sender
                                                      : message.receiver;
-      let index = validNumbers.indexOf(number);
-      ok(index >= 0, "message.number");
-      validNumbers.splice(index, 1); // Remove from validNumbers.
-    }
+        } else {
+          number = message.receivers[0];
+        }
 
-    tasks.next();
-  }, filter);
+        let index = aValidNumbers.indexOf(number);
+        ok(index >= 0, "message.number");
+        aValidNumbers.splice(index, 1); // Remove from aValidNumbers.
+      }
+
+      is(aValidNumbers.length, 0, "aValidNumbers.length");
+    });
 }
 
-tasks.push(function verifyInitialState() {
-  log("Verifying initial state.");
-  manager = window.navigator.mozMobileMessage;
-  ok(manager instanceof MozMobileMessageManager,
-     "manager is instance of " + manager.constructor);
-  tasks.next();
-});
-
-tasks.push(deleteAllMessages);
-
-/**
- * Populate database with messages to being tests. We'll have NUM_THREADS
- * sent and received messages.
- *
- *   send    to   "+15555315550"
- *   receive from "5555315550", count = 1
- *
- *   send    to   "+15555315551"
- *   receive from "5555315551", count = 2
- *   ...
- *   send    to   "+15555315559"
- *   receive from "5555315559", count = 10
- */
-tasks.push(function populateMessages() {
-  log("Populating messages.");
-  let count = 0;
-
-  function sendMessage(iter) {
-    let request = manager.send(REMOTE_INTERNATIONAL_NUMBER + iter,
-                               "Nice to meet you");
-    request.onsuccess = function onRequestSuccess(event) {
-      sendSmsToEmulator(REMOTE_NATIONAL_NUMBER + iter,
-                        "Nice to meet you, too");
-    }
-    request.onerror = function onRequestError(event) {
-      tasks.finish();
-    }
-  }
+startTestCommon(function testCaseMain() {
+  return Promise.resolve()
 
-  manager.addEventListener("received", function onReceived(event) {
-    ++count;
-    if (count < NUM_THREADS) {
-      sendMessage(count);
-    } else {
-      manager.removeEventListener("received", onReceived);
-      tasks.next();
-    }
-  });
-
-  sendMessage(count);
-});
-
-tasks.push(function() {
-  log("Verifying number of messages in database");
-  getAllMessages(function(messages) {
-    is(messages.length, NUM_THREADS * 2,
-       "should have exactly " + (NUM_THREADS * 2) + " messages");
-
-    tasks.next();
-  });
-});
+    // SMS, MO:international, MT:national
+    .then(() => sendSmsWithSuccess(REMOTE_INTERNATIONAL_NUMBER + '1', TEXT_1))
+    .then(() => sendTextSmsToEmulator(REMOTE_NATIONAL_NUMBER + '1', TEXT_2))
+    // SMS, MO:national, MT:international
+    .then(() => sendSmsWithSuccess(REMOTE_NATIONAL_NUMBER + '2', TEXT_1))
+    .then(() => sendTextSmsToEmulator(REMOTE_INTERNATIONAL_NUMBER + '2', TEXT_2))
+    // MMS, international
+    .then(() => sendMmsWithFailure(genFailingMms([REMOTE_INTERNATIONAL_NUMBER + '3'])))
+    // MMS, national
+    .then(() => sendMmsWithFailure(genFailingMms([REMOTE_NATIONAL_NUMBER + '3'])))
+    // SMS, MO:international, MT:national, ambiguous number.
+    .then(() => sendSmsWithSuccess(REMOTE_INTERNATIONAL_NUMBER + '4', TEXT_1))
+    .then(() => sendTextSmsToEmulator(REMOTE_NATIONAL_NUMBER + '4', TEXT_2))
+    .then(() => sendMmsWithFailure(genFailingMms(["jkalbcjklg"])))
+    .then(() => sendMmsWithFailure(genFailingMms(["jk.alb.cjk.lg"])))
+    // MMS, email
+    .then(() => sendMmsWithFailure(genFailingMms(["jk@alb.cjk.lg"])))
+    // MMS, IPv4
+    .then(() => sendMmsWithFailure(genFailingMms(["55.252.255.54"])))
+    // MMS, IPv6
+    .then(() => sendMmsWithFailure(genFailingMms(["5:5:2:5:2:2:55:54"])))
 
-for (let iter = 0; iter < NUM_THREADS; iter++) {
-  let national = REMOTE_NATIONAL_NUMBER + iter;
-  let international = REMOTE_INTERNATIONAL_NUMBER + iter;
-  tasks.push(checkMessage.bind(null, national, international));
-  tasks.push(checkMessage.bind(null, international, national));
-}
-
-tasks.push(deleteAllMessages);
-
-// WARNING: All tasks should be pushed before this!!!
-tasks.push(function cleanUp() {
-  if (pendingEmulatorCmdCount) {
-    window.setTimeout(cleanUp, 100);
-    return;
-  }
-
-  SpecialPowers.removePermission("sms", document);
-  SpecialPowers.clearUserPref("dom.sms.enabled");
-  finish();
+    .then(() => checkMessage(REMOTE_INTERNATIONAL_NUMBER + '1',
+                             [ REMOTE_INTERNATIONAL_NUMBER + '1',
+                               REMOTE_NATIONAL_NUMBER + '1' ]))
+    .then(() => checkMessage(REMOTE_NATIONAL_NUMBER + '2',
+                             [ REMOTE_NATIONAL_NUMBER + '2',
+                               REMOTE_INTERNATIONAL_NUMBER + '2' ]))
+    .then(() => checkMessage(REMOTE_INTERNATIONAL_NUMBER + '3',
+                             [ REMOTE_INTERNATIONAL_NUMBER + '3',
+                               REMOTE_NATIONAL_NUMBER + '3' ]))
+    .then(() => checkMessage(REMOTE_NATIONAL_NUMBER + '3',
+                             [ REMOTE_NATIONAL_NUMBER + '3',
+                               REMOTE_INTERNATIONAL_NUMBER + '3' ]))
+    .then(() => checkMessage(REMOTE_NATIONAL_NUMBER + '4',
+                             [ REMOTE_NATIONAL_NUMBER + '4',
+                               REMOTE_INTERNATIONAL_NUMBER + '4',
+                               "jkalbcjklg",
+                               "jk.alb.cjk.lg" ]))
+    .then(() => checkMessage("jk@alb.cjk.lg", ["jk@alb.cjk.lg"]))
+    .then(() => checkMessage("55.252.255.54", ["55.252.255.54"]))
+    .then(() => checkMessage("5:5:2:5:2:2:55:54", ["5:5:2:5:2:2:55:54"]))
 });
-
-tasks.run();
--- a/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js
@@ -1,116 +1,134 @@
 /* Any copyright is dedicated to the Public Domain.
  * http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
-MARIONETTE_HEAD_JS = 'head.js';
+MARIONETTE_HEAD_JS = 'mmdb_head.js';
 
-const RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID =
-  "@mozilla.org/mobilemessage/rilmobilemessagedatabaseservice;1";
+const DBNAME = "test_mmdb_setmessagedeliverybyid_sms:" + newUUID();
 
 const RECEIVER = "+1234567890";
 const TEXT = "The quick brown fox jumps over the lazy dog.";
 
+const MESSAGE_STORE_NAME = "sms";
+
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
 const DELIVERY_RECEIVED = "received";
 const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
 const DELIVERY_ERROR = "error";
 
 const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
 const DELIVERY_STATUS_SUCCESS = "success";
 const DELIVERY_STATUS_PENDING = "pending";
 const DELIVERY_STATUS_ERROR = "error";
 
-let dbService;
+function verify(aMmdb, aMessageId, aDelivery, aDeliveryStatus, aRv, aDomMessage) {
+  log("  Verify(" + aMessageId + ", " + aDelivery + ", " + aDeliveryStatus + ")");
 
-/**
- * @param aMessageId
- * @param aParams
- *        An array of four elements [<delivery>, <deliveryStatus>,
- *        <expected delivery>, <expected deliveryStatus>].
- */
-function setMessageDeliveryByMessageId(aMessageId, aParams) {
-  if (!dbService) {
-    dbService = Cc[RIL_MOBILEMESSAGEDATABASESERVICE_CONTRACTID]
-                .getService(Ci.nsIRilMobileMessageDatabaseService);
-    if (!dbService) {
-      log("  Failed to get database service.");
-      return Promise.reject();
-    }
-  }
+  ok(Components.isSuccessCode(aRv), "Components.isSuccessCode(" + aRv + ")");
+  ok(aDomMessage, "DOM message validity");
+  is(aDomMessage.delivery, aDelivery, "message.delivery");
+  is(aDomMessage.deliveryStatus, aDeliveryStatus, "message.deliveryStatus");
 
   let deferred = Promise.defer();
 
-  log("  Set to " + aParams[0] + ":" + aParams[1]);
-  dbService.setMessageDeliveryByMessageId(aMessageId, null, aParams[0],
-                                          aParams[1], null,
-                                          function(aRv, aDomMessage) {
-    if (aRv !== Cr.NS_OK) {
-      deferred.reject(aRv);
-      return;
-    }
+  // Verify deliveryIndex, sentTimestamp and deliveryTimestamp
+  aMmdb.newTxn("readonly", function(aError, aTransaction, aMessageStore) {
+    let messageRecord;
+    aTransaction.oncomplete = function() {
+      ok(true, "transaction complete");
 
-    is(aDomMessage.delivery, aParams[2], "message.delivery");
-    is(aDomMessage.deliveryStatus, aParams[3], "message.deliveryStatus");
+      is(messageRecord.deliveryIndex[0], aDelivery,
+         "messageRecord.deliveryIndex[0]");
+      is(messageRecord.deliveryIndex[1], messageRecord.timestamp,
+         "messageRecord.deliveryIndex[1]");
+      if (aDelivery == DELIVERY_SENT) {
+        ok(messageRecord.sentTimestamp >= messageRecord.timestamp,
+           "messageRecord.sentTimestamp");
+      }
+      if (aDeliveryStatus == DELIVERY_STATUS_SUCCESS) {
+        ok(messageRecord.deliveryTimestamp >= messageRecord.timestamp,
+           "messageRecord.deliveryTimestamp");
+      }
 
-    deferred.resolve([aRv, aDomMessage]);
-  });
+      deferred.resolve(aMmdb);
+    };
+
+    aMessageStore.get(aMessageId).onsuccess = function(event) {
+      messageRecord = event.target.result;
+      ok(true, "Got messageRecord " + messageRecord.id);
+    };
+  }, [MESSAGE_STORE_NAME]);
 
   return deferred.promise;
 }
 
-function test(aTitle, aParamArray) {
+function test(aTitle, aMmdb, aDeliveryStatusRequested, aParamArray) {
   log(aTitle);
 
-  return sendSmsWithSuccess(RECEIVER, TEXT)
-    .then(function(aDomMessage) {
-      let id = aDomMessage.id;
-
-      let promise = Promise.resolve();
+  let message = {
+    type: "sms",
+    sender: null,
+    timestamp: Date.now(),
+    deliveryStatusRequested: aDeliveryStatusRequested,
+    receiver: RECEIVER,
+    iccId: null,
+    body: TEXT,
+  };
+  return saveSendingMessage(aMmdb, message)
+    .then(function(aValues) {
+      let [resultCode, domMessage] = aValues;
+      let promise =
+        verify(aMmdb, domMessage.id, DELIVERY_SENDING,
+               (aDeliveryStatusRequested ? DELIVERY_STATUS_PENDING
+                                         : DELIVERY_STATUS_NOT_APPLICABLE),
+               resultCode, domMessage);
       while (aParamArray.length) {
         let params = aParamArray.shift();
-        promise =
-          promise.then(setMessageDeliveryByMessageId.bind(null, id, params));
+        let v = verify.bind(null, aMmdb, domMessage.id, params[2], params[3]);
+        promise = promise
+          .then(() => {
+            log("  Set to " + params[0] + ":" + params[1]);
+            return setMessageDeliveryByMessageId(aMmdb, domMessage.id, null,
+                                                 params[0], params[1], null);
+          })
+          .then((aValues) => v(aValues[0], aValues[1]));
       }
 
       return promise;
     });
 }
 
-startTestCommon(function testCaseMain() {
-  return Promise.resolve()
-    .then(test.bind(null, "Simulate send failed without delivery report requisition", [
-      [DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE,
-       DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE],
+startTestBase(function testCaseMain() {
+  return initMobileMessageDB(newMobileMessageDB(), DBNAME, 0)
+    .then((aMmdb) => test("Simulate send failed without delivery report requisition",
+                          aMmdb, false, [
       [DELIVERY_ERROR, DELIVERY_STATUS_ERROR,
        DELIVERY_ERROR, DELIVERY_STATUS_ERROR],
     ]))
-    .then(test.bind(null, "Simulate send failed with delivery report requisition", [
-      [DELIVERY_SENDING, DELIVERY_STATUS_PENDING,
-       DELIVERY_SENDING, DELIVERY_STATUS_PENDING],
+    .then((aMmdb) => test("Simulate send failed with delivery report requisition",
+                          aMmdb, true, [
       [DELIVERY_ERROR, DELIVERY_STATUS_ERROR,
        DELIVERY_ERROR, DELIVERY_STATUS_ERROR],
     ]))
-    .then(test.bind(null, "Simulate sent without delivery report requisition", [
-      [DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE,
-       DELIVERY_SENDING, DELIVERY_STATUS_NOT_APPLICABLE],
+    .then((aMmdb) => test("Simulate sent without delivery report requisition",
+                          aMmdb, false, [
       [DELIVERY_SENT, null,
        DELIVERY_SENT, DELIVERY_STATUS_NOT_APPLICABLE],
     ]))
-    .then(test.bind(null, "Simulate sent with delivery report success", [
-      [DELIVERY_SENDING, DELIVERY_STATUS_PENDING,
-       DELIVERY_SENDING, DELIVERY_STATUS_PENDING],
+    .then((aMmdb) => test("Simulate sent with delivery report success",
+                          aMmdb, true, [
       [DELIVERY_SENT, null,
        DELIVERY_SENT, DELIVERY_STATUS_PENDING],
       [null, DELIVERY_STATUS_SUCCESS,
        DELIVERY_SENT, DELIVERY_STATUS_SUCCESS],
     ]))
-    .then(test.bind(null, "Simulate sent with delivery report error", [
-      [DELIVERY_SENDING, DELIVERY_STATUS_PENDING,
-       DELIVERY_SENDING, DELIVERY_STATUS_PENDING],
+    .then((aMmdb) => test("Simulate sent with delivery report error",
+                          aMmdb, true, [
       [DELIVERY_SENT, null,
        DELIVERY_SENT, DELIVERY_STATUS_PENDING],
       [null, DELIVERY_ERROR,
        DELIVERY_SENT, DELIVERY_ERROR],
-    ]));
+    ]))
+    .then(closeMobileMessageDB);
 });
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js
@@ -0,0 +1,737 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'mmdb_head.js';
+
+Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
+
+let RIL = {};
+Cu.import("resource://gre/modules/ril_consts.js", RIL);
+
+let MMS = {};
+Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS);
+
+const DBNAME = "test_mmdb_upgradeSchema_22:" + newUUID();
+
+const MESSAGE_STORE_NAME = "sms";
+const THREAD_STORE_NAME = "thread";
+const PARTICIPANT_STORE_NAME = "participant";
+
+const DEBUG = false;
+
+const READ_WRITE = "readwrite";
+
+const DELIVERY_SENDING = "sending";
+const DELIVERY_SENT = "sent";
+const DELIVERY_RECEIVED = "received";
+const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
+const DELIVERY_ERROR = "error";
+
+const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
+const DELIVERY_STATUS_SUCCESS = "success";
+const DELIVERY_STATUS_PENDING = "pending";
+const DELIVERY_STATUS_ERROR = "error";
+
+const MESSAGE_CLASS_NORMAL = "normal";
+
+const FILTER_READ_UNREAD = 0;
+const FILTER_READ_READ = 1;
+
+const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
+
+let LEGACY = {
+  saveRecord: function(aMessageRecord, aAddresses, aCallback) {
+    if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
+
+    let self = this;
+    this.newTxn(READ_WRITE, function(error, txn, stores) {
+      let notifyResult = function(aRv, aMessageRecord) {
+        if (!aCallback) {
+          return;
+        }
+        let domMessage =
+          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
+        aCallback.notify(aRv, domMessage);
+      };
+
+      if (error) {
+        notifyResult(error, null);
+        return;
+      }
+
+      txn.oncomplete = function oncomplete(event) {
+        if (aMessageRecord.id > self.lastMessageId) {
+          self.lastMessageId = aMessageRecord.id;
+        }
+        notifyResult(Cr.NS_OK, aMessageRecord);
+      };
+      txn.onabort = function onabort(event) {
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE, null);
+      };
+
+      let messageStore = stores[0];
+      let participantStore = stores[1];
+      let threadStore = stores[2];
+      LEGACY.replaceShortMessageOnSave.call(self, txn, messageStore,
+                                            participantStore, threadStore,
+                                            aMessageRecord, aAddresses);
+    }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
+  },
+
+  replaceShortMessageOnSave: function(aTransaction, aMessageStore,
+                                      aParticipantStore, aThreadStore,
+                                      aMessageRecord, aAddresses) {
+    let isReplaceTypePid = (aMessageRecord.pid) &&
+                           ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 &&
+                             aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) ||
+                            aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE);
+
+    if (aMessageRecord.type != "sms" ||
+        aMessageRecord.delivery != DELIVERY_RECEIVED ||
+        !isReplaceTypePid) {
+      LEGACY.realSaveRecord.call(this, aTransaction, aMessageStore,
+                                 aParticipantStore, aThreadStore,
+                                 aMessageRecord, aAddresses);
+      return;
+    }
+
+    // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)":
+    //
+    //   ... the MS shall check the originating address and replace any
+    //   existing stored message having the same Protocol Identifier code
+    //   and originating address with the new short message and other
+    //   parameter values. If there is no message to be replaced, the MS
+    //   shall store the message in the normal way. ... it is recommended
+    //   that the SC address should not be checked by the MS."
+    let self = this;
+    this.findParticipantRecordByPlmnAddress(aParticipantStore,
+                                            aMessageRecord.sender, false,
+                                            function(participantRecord) {
+      if (!participantRecord) {
+        LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore,
+                                   aParticipantStore, aThreadStore,
+                                   aMessageRecord, aAddresses);
+        return;
+      }
+
+      let participantId = participantRecord.id;
+      let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]);
+      let request = aMessageStore.index("participantIds").openCursor(range);
+      request.onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (!cursor) {
+          LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore,
+                                     aParticipantStore, aThreadStore,
+                                     aMessageRecord, aAddresses);
+          return;
+        }
+
+        // A message record with same participantId found.
+        // Verify matching criteria.
+        let foundMessageRecord = cursor.value;
+        if (foundMessageRecord.type != "sms" ||
+            foundMessageRecord.sender != aMessageRecord.sender ||
+            foundMessageRecord.pid != aMessageRecord.pid) {
+          cursor.continue();
+          return;
+        }
+
+        // Match! Now replace that found message record with current one.
+        aMessageRecord.id = foundMessageRecord.id;
+        LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore,
+                                   aParticipantStore, aThreadStore,
+                                   aMessageRecord, aAddresses);
+      };
+    });
+  },
+
+  realSaveRecord: function(aTransaction, aMessageStore, aParticipantStore,
+                           aThreadStore, aMessageRecord, aAddresses) {
+    let self = this;
+    this.findThreadRecordByPlmnAddresses(aThreadStore, aParticipantStore,
+                                         aAddresses, true,
+                                         function(threadRecord, participantIds) {
+      if (!participantIds) {
+        aTransaction.abort();
+        return;
+      }
+
+      let isOverriding = (aMessageRecord.id !== undefined);
+      if (!isOverriding) {
+        // |self.lastMessageId| is only updated in |txn.oncomplete|.
+        aMessageRecord.id = self.lastMessageId + 1;
+      }
+
+      let timestamp = aMessageRecord.timestamp;
+      let insertMessageRecord = function(threadId) {
+        // Setup threadId & threadIdIndex.
+        aMessageRecord.threadId = threadId;
+        aMessageRecord.threadIdIndex = [threadId, timestamp];
+        // Setup participantIdsIndex.
+        aMessageRecord.participantIdsIndex = [];
+        for each (let id in participantIds) {
+          aMessageRecord.participantIdsIndex.push([id, timestamp]);
+        }
+
+        if (!isOverriding) {
+          // Really add to message store.
+          aMessageStore.put(aMessageRecord);
+          return;
+        }
+
+        // If we're going to override an old message, we need to update the
+        // info of the original thread containing the overridden message.
+        // To get the original thread ID and read status of the overridden
+        // message record, we need to retrieve it before overriding it.
+        aMessageStore.get(aMessageRecord.id).onsuccess = function(event) {
+          let oldMessageRecord = event.target.result;
+          aMessageStore.put(aMessageRecord);
+          if (oldMessageRecord) {
+            self.updateThreadByMessageChange(aMessageStore,
+                                             aThreadStore,
+                                             oldMessageRecord.threadId,
+                                             aMessageRecord.id,
+                                             oldMessageRecord.read);
+          }
+        };
+      };
+
+      if (threadRecord) {
+        let needsUpdate = false;
+
+        if (threadRecord.lastTimestamp <= timestamp) {
+          let lastMessageSubject;
+          if (aMessageRecord.type == "mms") {
+            lastMessageSubject = aMessageRecord.headers.subject;
+          }
+          threadRecord.lastMessageSubject = lastMessageSubject || null;
+          threadRecord.lastTimestamp = timestamp;
+          threadRecord.body = aMessageRecord.body;
+          threadRecord.lastMessageId = aMessageRecord.id;
+          threadRecord.lastMessageType = aMessageRecord.type;
+          needsUpdate = true;
+        }
+
+        if (!aMessageRecord.read) {
+          threadRecord.unreadCount++;
+          needsUpdate = true;
+        }
+
+        if (needsUpdate) {
+          aThreadStore.put(threadRecord);
+        }
+
+        insertMessageRecord(threadRecord.id);
+        return;
+      }
+
+      let lastMessageSubject;
+      if (aMessageRecord.type == "mms") {
+        lastMessageSubject = aMessageRecord.headers.subject;
+      }
+
+      threadRecord = {
+        participantIds: participantIds,
+        participantAddresses: aAddresses,
+        lastMessageId: aMessageRecord.id,
+        lastTimestamp: timestamp,
+        lastMessageSubject: lastMessageSubject || null,
+        body: aMessageRecord.body,
+        unreadCount: aMessageRecord.read ? 0 : 1,
+        lastMessageType: aMessageRecord.type,
+      };
+      aThreadStore.add(threadRecord).onsuccess = function(event) {
+        let threadId = event.target.result;
+        insertMessageRecord(threadId);
+      };
+    });
+  },
+
+  fillReceivedMmsThreadParticipants: function(aMessage, threadParticipants) {
+    let receivers = aMessage.receivers;
+    // If we don't want to disable the MMS grouping for receiving, we need to
+    // add the receivers (excluding the user's own number) to the participants
+    // for creating the thread. Some cases might be investigated as below:
+    //
+    // 1. receivers.length == 0
+    //    This usually happens when receiving an MMS notification indication
+    //    which doesn't carry any receivers.
+    // 2. receivers.length == 1
+    //    If the receivers contain single phone number, we don't need to
+    //    add it into participants because we know that number is our own.
+    // 3. receivers.length >= 2
+    //    If the receivers contain multiple phone numbers, we need to add all
+    //    of them but not the user's own number into participants.
+    if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) {
+      return;
+    }
+    let isSuccess = false;
+    let slicedReceivers = receivers.slice();
+    if (aMessage.msisdn) {
+      let found = slicedReceivers.indexOf(aMessage.msisdn);
+      if (found !== -1) {
+        isSuccess = true;
+        slicedReceivers.splice(found, 1);
+      }
+    }
+
+    if (!isSuccess) {
+      // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's
+      // own phone number), so we cannot correcly exclude the user's own
+      // number from the receivers, thus wrongly building the thread index.
+      if (DEBUG) debug("Error! Cannot strip out user's own phone number!");
+    }
+
+    threadParticipants = threadParticipants.concat(slicedReceivers);
+  },
+
+  saveReceivedMessage: function(aMessage, aCallback) {
+    if ((aMessage.type != "sms" && aMessage.type != "mms") ||
+        (aMessage.type == "sms" && (aMessage.messageClass == undefined ||
+                                    aMessage.sender == undefined)) ||
+        (aMessage.type == "mms" && (aMessage.delivery == undefined ||
+                                    aMessage.deliveryStatus == undefined ||
+                                    !Array.isArray(aMessage.receivers))) ||
+        aMessage.timestamp == undefined) {
+      if (aCallback) {
+        aCallback.notify(Cr.NS_ERROR_FAILURE, null);
+      }
+      return;
+    }
+
+    let threadParticipants;
+    if (aMessage.type == "mms") {
+      if (aMessage.headers.from) {
+        aMessage.sender = aMessage.headers.from.address;
+      } else {
+        aMessage.sender = "anonymous";
+      }
+
+      threadParticipants = [aMessage.sender];
+      LEGACY.fillReceivedMmsThreadParticipants.call(this, aMessage,
+                                                    threadParticipants);
+    } else { // SMS
+      threadParticipants = [aMessage.sender];
+    }
+
+    let timestamp = aMessage.timestamp;
+
+    // Adding needed indexes and extra attributes for internal use.
+    // threadIdIndex & participantIdsIndex are filled in saveRecord().
+    aMessage.readIndex = [FILTER_READ_UNREAD, timestamp];
+    aMessage.read = FILTER_READ_UNREAD;
+
+    // If |sentTimestamp| is not specified, use 0 as default.
+    if (aMessage.sentTimestamp == undefined) {
+      aMessage.sentTimestamp = 0;
+    }
+
+    if (aMessage.type == "mms") {
+      aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"];
+      aMessage.isReadReportSent = false;
+
+      // As a receiver, we don't need to care about the delivery status of
+      // others, so we put a single element with self's phone number in the
+      // |deliveryInfo| array.
+      aMessage.deliveryInfo = [{
+        receiver: aMessage.phoneNumber,
+        deliveryStatus: aMessage.deliveryStatus,
+        deliveryTimestamp: 0,
+        readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE,
+        readTimestamp: 0,
+      }];
+
+      delete aMessage.deliveryStatus;
+    }
+
+    if (aMessage.type == "sms") {
+      aMessage.delivery = DELIVERY_RECEIVED;
+      aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS;
+      aMessage.deliveryTimestamp = 0;
+
+      if (aMessage.pid == undefined) {
+        aMessage.pid = RIL.PDU_PID_DEFAULT;
+      }
+    }
+    aMessage.deliveryIndex = [aMessage.delivery, timestamp];
+
+    LEGACY.saveRecord.call(this, aMessage, threadParticipants, aCallback);
+  },
+
+  saveSendingMessage: function(aMessage, aCallback) {
+    if ((aMessage.type != "sms" && aMessage.type != "mms") ||
+        (aMessage.type == "sms" && aMessage.receiver == undefined) ||
+        (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) ||
+        aMessage.deliveryStatusRequested == undefined ||
+        aMessage.timestamp == undefined) {
+      if (aCallback) {
+        aCallback.notify(Cr.NS_ERROR_FAILURE, null);
+      }
+      return;
+    }
+
+    // Set |aMessage.deliveryStatus|. Note that for MMS record
+    // it must be an array of strings; For SMS, it's a string.
+    let deliveryStatus = aMessage.deliveryStatusRequested
+                       ? DELIVERY_STATUS_PENDING
+                       : DELIVERY_STATUS_NOT_APPLICABLE;
+    if (aMessage.type == "sms") {
+      aMessage.deliveryStatus = deliveryStatus;
+      // If |deliveryTimestamp| is not specified, use 0 as default.
+      if (aMessage.deliveryTimestamp == undefined) {
+        aMessage.deliveryTimestamp = 0;
+      }
+    } else if (aMessage.type == "mms") {
+      let receivers = aMessage.receivers
+      if (!Array.isArray(receivers)) {
+        if (DEBUG) {
+          debug("Need receivers for MMS. Fail to save the sending message.");
+        }
+        if (aCallback) {
+          aCallback.notify(Cr.NS_ERROR_FAILURE, null);
+        }
+        return;
+      }
+      let readStatus = aMessage.headers["x-mms-read-report"]
+                     ? MMS.DOM_READ_STATUS_PENDING
+                     : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
+      aMessage.deliveryInfo = [];
+      for (let i = 0; i < receivers.length; i++) {
+        aMessage.deliveryInfo.push({
+          receiver: receivers[i],
+          deliveryStatus: deliveryStatus,
+          deliveryTimestamp: 0,
+          readStatus: readStatus,
+          readTimestamp: 0,
+        });
+      }
+    }
+
+    let timestamp = aMessage.timestamp;
+
+    // Adding needed indexes and extra attributes for internal use.
+    // threadIdIndex & participantIdsIndex are filled in saveRecord().
+    aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp];
+    aMessage.readIndex = [FILTER_READ_READ, timestamp];
+    aMessage.delivery = DELIVERY_SENDING;
+    aMessage.messageClass = MESSAGE_CLASS_NORMAL;
+    aMessage.read = FILTER_READ_READ;
+
+    // |sentTimestamp| is not available when the message is still sedning.
+    aMessage.sentTimestamp = 0;
+
+    let addresses;
+    if (aMessage.type == "sms") {
+      addresses = [aMessage.receiver];
+    } else if (aMessage.type == "mms") {
+      addresses = aMessage.receivers;
+    }
+    LEGACY.saveRecord.call(this, aMessage, addresses, aCallback);
+  },
+};
+
+function callMmdbMethodLegacy(aMmdb, aMethodName) {
+  let deferred = Promise.defer();
+
+  let args = Array.slice(arguments, 2);
+  args.push({
+    notify: function(aRv) {
+      if (!Components.isSuccessCode(aRv)) {
+        ok(true, aMethodName + " returns a unsuccessful code: " + aRv);
+        deferred.reject(Array.slice(arguments));
+      } else {
+        ok(true, aMethodName + " returns a successful code: " + aRv);
+        deferred.resolve(Array.slice(arguments));
+      }
+    }
+  });
+  LEGACY[aMethodName].apply(aMmdb, args);
+
+  return deferred.promise;
+}
+
+function saveSendingMessageLegacy(aMmdb, aMessage) {
+  return callMmdbMethodLegacy(aMmdb, "saveSendingMessage", aMessage);
+}
+
+function saveReceivedMessageLegacy(aMmdb, aMessage) {
+  return callMmdbMethodLegacy(aMmdb, "saveReceivedMessage", aMessage);
+}
+
+// Have a long long subject causes the send fails, so we don't need
+// networking here.
+const MMS_MAX_LENGTH_SUBJECT = 40;
+function genMmsSubject(sep) {
+  return "Hello " + (new Array(MMS_MAX_LENGTH_SUBJECT).join(sep)) + " World!";
+}
+
+function generateMms(aSender, aReceivers, aDelivery) {
+  let message = {
+    headers: {},
+    type: "mms",
+    timestamp: Date.now(),
+    receivers: aReceivers,
+    subject: genMmsSubject(' '),
+    attachments: [],
+  };
+
+  message.headers.subject = message.subject;
+  message.headers.to = [];
+  for (let i = 0; i < aReceivers.length; i++) {
+    let receiver = aReceivers[i];
+    let entry = { type: MMS.Address.resolveType(receiver) };
+    if (entry.type == "PLMN") {
+      entry.address = PhoneNumberUtils.normalize(receiver, false);
+    } else {
+      entry.address = receiver;
+    }
+    ok(true, "MMS to address '" + receiver +"' resolved as type " + entry.type);
+    message.headers.to.push(entry);
+  }
+  if (aSender) {
+    message.headers.from = {
+      address: aSender,
+      type: MMS.Address.resolveType(aSender)
+    };
+    ok(true, "MMS from address '" + aSender +"' resolved as type " +
+       message.headers.from.type);
+  }
+
+  if ((aDelivery === DELIVERY_RECEIVED) ||
+      (aDelivery === DELIVERY_NOT_DOWNLOADED)) {
+    message.delivery = aDelivery;
+    message.deliveryStatus = DELIVERY_STATUS_SUCCESS;
+  } else {
+    message.deliveryStatusRequested = false;
+  }
+
+  return message;
+}
+
+function generateSms(aSender, aReceiver, aDelivery) {
+  let message = {
+    type: "sms",
+    sender: aSender,
+    timestamp: Date.now(),
+    receiver: aReceiver,
+    body: "The snow grows white on the mountain tonight.",
+  };
+
+  if (aDelivery === DELIVERY_RECEIVED) {
+    message.messageClass = MESSAGE_CLASS_NORMAL;
+  } else {
+    message.deliveryStatusRequested = false;
+  }
+
+  return message;
+}
+
+function matchArray(lhs, rhs) {
+  if (rhs.length != lhs.length) {
+    return false;
+  }
+
+  for (let k = 0; k < lhs.length; k++) {
+    if (lhs[k] != rhs[k]) {
+      return false;
+    }
+  }
+
+  return true;
+}
+
+const TEST_ADDRESSES = [
+  "+15525225554",      // MMS, TYPE=PLMN
+  "5525225554",        // MMS, TYPE=PLMN
+  "jkalbcjklg",        // MMS, TYPE=PLMN, because of PhoneNumberNormalizer
+  "jk.alb.cjk.lg",     // MMS, TYPE=PLMN, because of PhoneNumberNormalizer
+  "j:k:a:l:b:c:jk:lg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer
+  "55.252.255.54",     // MMS, TYPE=IPv4
+  "5:5:2:5:2:2:55:54", // MMS, TYPE=IPv6
+  "jk@alb.cjk.lg",     // MMS, TYPE=email
+  "___"                // MMS, TYPE=Others
+];
+
+function populateDatabase(aMmdb) {
+  log("Populating database:");
+
+  let promise = Promise.resolve()
+
+    // We're generating other messages that would be identified as the same
+    // participant with "+15525225554".
+    .then(() => saveReceivedMessageLegacy(
+      aMmdb, generateSms("+15525225554", null, DELIVERY_RECEIVED)))
+
+    // SMS, national number.
+    .then(() => saveReceivedMessageLegacy(
+      aMmdb, generateSms("5525225554", null, DELIVERY_RECEIVED)));
+
+  for (let i = 0; i < TEST_ADDRESSES.length; i++) {
+    let address = TEST_ADDRESSES[i];
+    promise = promise.then(() => saveReceivedMessageLegacy(
+      aMmdb, generateMms(address, ["a"], DELIVERY_RECEIVED)));
+  }
+
+  // Permutation of TEST_ADDRESSES.
+  for (let i = 0; i < TEST_ADDRESSES.length; i++) {
+    for (let j = i + 1; j < TEST_ADDRESSES.length; j++) {
+      let addr_i = TEST_ADDRESSES[i], addr_j = TEST_ADDRESSES[j];
+      promise = promise.then(() => saveSendingMessageLegacy(
+        aMmdb, generateMms(null, [addr_i, addr_j], DELIVERY_SENDING)));
+    }
+  }
+
+  // At this time, we have 3 threads, whose |participants| are:
+  // ["+15525225554"], ["___"], and ["+15525225554", "___"].  The number of each
+  // thread are [ (2 + 8 + 8 * 7 / 2), 1, 8 ] = [ 38, 1, 8 ].
+
+  return promise;
+}
+
+function doVerifyDatabase(aMmdb, aExpected) {
+  // 1) retrieve all threads.
+  return createThreadCursor(aMmdb)
+    .then(function(aValues) {
+      let [errorCode, domThreads] = aValues;
+      is(errorCode, 0, "errorCode");
+      is(domThreads.length, aExpected.length, "domThreads.length");
+
+      let numMessagesInThread = [];
+      let totalMessages = 0;
+
+      for (let i = 0; i < domThreads.length; i++) {
+        let domThread = domThreads[i];
+        log("  thread<" + domThread.id + "> : " + domThread.participants);
+
+        let index = (function() {
+          let rhs = domThread.participants;
+          for (let j = 0; j < aExpected.length; j++) {
+            let lhs = aExpected[j].participants;
+            if (matchArray(lhs, rhs)) {
+              return j;
+            }
+          }
+        })();
+        // 2) make sure all retrieved threads are in expected array.
+        ok(index >= 0, "validity of domThread.participants");
+
+        // 3) fill out numMessagesInThread, which is a <thread id> =>
+        // <num messages> map.
+        numMessagesInThread[domThread.id] = aExpected[index].messages;
+        totalMessages += aExpected[index].messages
+
+        aExpected.splice(index, 1);
+      }
+
+      // 4) make sure no thread is missing by checking |aExpected.length == 0|.
+      is(aExpected.length, 0, "remaining unmatched threads");
+
+      // 5) retrieve all messages.
+      return createMessageCursor(aMmdb, {})
+        .then(function(aValues) {
+          let [errorCode, domMessages] = aValues;
+          is(errorCode, 0, "errorCode");
+          // 6) check total number of messages.
+          is(domMessages.length, totalMessages, "domMessages.length");
+
+          for (let i = 0; i < domMessages.length; i++) {
+            let domMessage = domMessages[i];
+            // 7) make sure message thread id is valid by checking
+            //    |numMessagesInThread[domMessage.threadId] != null|.
+            ok(numMessagesInThread[domMessage.threadId] != null,
+               "domMessage.threadId");
+
+            // 8) for each message, reduce
+            // |numMessagesInThread[domMessage.threadId]| by 1.
+            --numMessagesInThread[domMessage.threadId];
+            ok(true, "numMessagesInThread[" + domMessage.threadId + "] = " +
+                     numMessagesInThread[domMessage.threadId]);
+          }
+
+          // 9) check if |numMessagesInThread| is now an array of all zeros.
+          for (let i = 0; i < domThreads.length; i++) {
+            let domThread = domThreads[i];
+            is(numMessagesInThread[domThread.id], 0,
+               "numMessagesInThread[" + domThread.id + "]");
+          }
+        });
+    });
+}
+
+function verifyDatabaseBeforeUpgrade(aMmdb) {
+  log("Before updateSchema22:");
+  return doVerifyDatabase(aMmdb, [{
+    participants: ["+15525225554"],
+    messages: 38 // 2 + (9 - 1) + (9 - 1) * ( 9 - 1 - 1) / 2
+  }, {
+    participants: ["___"],
+    messages: 1
+  }, {
+    participants: ["+15525225554", "___"],
+    messages: 8
+  }]);
+}
+
+function verifyDatabaseAfterUpgrade(aMmdb) {
+  log("After updateSchema22:");
+  return doVerifyDatabase(aMmdb, [{
+    participants: ["+15525225554"],
+    messages: 17 // 2 + 5 + 5 * (5 - 1) / 2
+  }, {
+    participants: ["55.252.255.54"],
+    messages: 1
+  }, {
+    participants: ["5:5:2:5:2:2:55:54"],
+    messages: 1
+  }, {
+    participants: ["jk@alb.cjk.lg"],
+    messages: 1
+  }, {
+    participants: ["___"],
+    messages: 1
+  }, {
+    participants: ["+15525225554", "55.252.255.54"],
+    messages: 5
+  }, {
+    participants: ["+15525225554", "5:5:2:5:2:2:55:54"],
+    messages: 5
+  }, {
+    participants: ["+15525225554", "jk@alb.cjk.lg"],
+    messages: 5
+  }, {
+    participants: ["+15525225554", "___"],
+    messages: 5
+  }, {
+    participants: ["55.252.255.54", "5:5:2:5:2:2:55:54"],
+    messages: 1
+  }, {
+    participants: ["55.252.255.54", "jk@alb.cjk.lg"],
+    messages: 1
+  }, {
+    participants: ["55.252.255.54", "___"],
+    messages: 1
+  }, {
+    participants: ["5:5:2:5:2:2:55:54", "jk@alb.cjk.lg"],
+    messages: 1
+  }, {
+    participants: ["5:5:2:5:2:2:55:54", "___"],
+    messages: 1
+  }, {
+    participants: ["jk@alb.cjk.lg", "___"],
+    messages: 1
+  }]);
+}
+
+startTestBase(function testCaseMain() {
+  let mmdb = newMobileMessageDB();
+  return initMobileMessageDB(mmdb, DBNAME, 22)
+    .then(() => populateDatabase(mmdb))
+    .then(() => verifyDatabaseBeforeUpgrade(mmdb))
+    .then(() => closeMobileMessageDB(mmdb))
+
+    .then(() => initMobileMessageDB(mmdb, DBNAME, 23))
+    .then(() => verifyDatabaseAfterUpgrade(mmdb))
+    .then(() => closeMobileMessageDB(mmdb));
+});
new file mode 100644
--- /dev/null
+++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js
@@ -0,0 +1,171 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 60000;
+MARIONETTE_HEAD_JS = 'mmdb_head.js';
+
+const DBNAME = "test_mmdb_upgradeSchema_current_structure:" + newUUID();
+
+const LAYOUT = {
+  sms: {
+    keyPath: "id",
+    autoIncrement: false,
+    indice: {
+      delivery: {
+        keyPath: "deliveryIndex",
+        multiEntry: false,
+        unique: false,
+      },
+
+      envelopeId: {
+        keyPath: "envelopeIdIndex",
+        multiEntry: false,
+        unique: true,
+      },
+
+      participantIds: {
+        keyPath: "participantIdsIndex",
+        multiEntry: true,
+        unique: false,
+      },
+
+      read: {
+        keyPath: "readIndex",
+        multiEntry: false,
+        unique: false,
+      },
+
+      threadId: {
+        keyPath: "threadIdIndex",
+        multiEntry: false,
+        unique: false,
+      },
+
+      timestamp: {
+        keyPath: "timestamp",
+        multiEntry: false,
+        unique: false,
+      },
+
+      transactionId: {
+        keyPath: "transactionIdIndex",
+        multiEntry: false,
+        unique: true,
+      }
+    },
+  },
+
+  thread: {
+    keyPath: "id",
+    autoIncrement: true,
+    indice: {
+      lastTimestamp: {
+        keyPath: "lastTimestamp",
+        multiEntry: false,
+        unique: false,
+      },
+
+      participantIds: {
+        keyPath: "participantIds",
+        multiEntry: false,
+        unique: false,
+      }
+    },
+  },
+
+  participant: {
+    keyPath: "id",
+    autoIncrement: true,
+    indice: {
+      addresses: {
+        keyPath: "addresses",
+        multiEntry: true,
+        unique: false,
+      }
+    },
+  },
+
+  "sms-segment": {
+    keyPath: "id",
+    autoIncrement: true,
+    indice: {
+      hash: {
+        keyPath: "hash",
+        multiEntry: false,
+        unique: true,
+      }
+    },
+  }
+};
+
+function verifyIndex(aIndex, aIndexLayout) {
+  log("  Verifying index '" + aIndex.name + "'");
+
+  is(aIndex.keyPath, aIndexLayout.keyPath, "aIndex.keyPath");
+  is(aIndex.multiEntry, aIndexLayout.multiEntry, "aIndex.multiEntry");
+  is(aIndex.unique, aIndexLayout.unique, "aIndex.unique");
+}
+
+function verifyStore(aObjectStore, aStoreLayout) {
+  log("Verifying object store '" + aObjectStore.name + "'");
+
+  is(aObjectStore.keyPath, aStoreLayout.keyPath, "aObjectStore.keyPath");
+  is(aObjectStore.autoIncrement, aStoreLayout.autoIncrement,
+     "aObjectStore.autoIncrement");
+
+  let expectedIndexNames = Object.keys(aStoreLayout.indice);
+  for (let i = 0; i < aObjectStore.indexNames.length; i++) {
+    let indexName = aObjectStore.indexNames.item(i);
+
+    let index = expectedIndexNames.indexOf(indexName);
+    ok(index >= 0, "Index name '" + indexName + "' validity");
+    expectedIndexNames.splice(index, 1);
+
+    verifyIndex(aObjectStore.index(indexName), aStoreLayout.indice[indexName]);
+  }
+
+  // All index names should have been verified and leaves expectedIndexNames an
+  // empty array.
+  is(expectedIndexNames.length, 0, "Extra indice: " + expectedIndexNames);
+}
+
+function verifyDatabase(aMmdb) {
+  let deferred = Promise.defer();
+
+  let expectedStoreNames = Object.keys(LAYOUT);
+  aMmdb.newTxn("readonly", function(aError, aTransaction, aObjectStores) {
+    if (!Array.isArray(aObjectStores)) {
+      // When we have only one object store open, aObjectStores is an instance
+      // of IDBObjectStore.  Push it to an array for convenience here.
+      aObjectStores = [aObjectStores];
+    }
+
+    is(aObjectStores.length, expectedStoreNames.length,
+       "expected number of object stores");
+
+    let slicedStoreNames = expectedStoreNames.slice();
+    for (let i = 0; i < aObjectStores.length; i++) {
+      let objectStore = aObjectStores[i];
+
+      let index = slicedStoreNames.indexOf(objectStore.name);
+      ok(index >= 0, "objectStore.name '" + objectStore.name + "' validity");
+      slicedStoreNames.splice(index, 1);
+
+      verifyStore(objectStore, LAYOUT[objectStore.name]);
+    }
+
+    // All store names should have been verified and leaves slicedStoreNames an
+    // empty array.
+    is(slicedStoreNames.length, 0, "Extra object stores: " + slicedStoreNames);
+
+    deferred.resolve(aMmdb);
+  }, expectedStoreNames);
+
+  return deferred.promise;
+}
+
+startTestBase(function testCaseMain() {
+  return initMobileMessageDB(newMobileMessageDB(), DBNAME, 0)
+    .then(verifyDatabase)
+    .then(closeMobileMessageDB);
+});
--- a/dom/mobilemessage/tests/test_mms_pdu_helper.js
+++ b/dom/mobilemessage/tests/test_mms_pdu_helper.js
@@ -45,79 +45,356 @@ add_test(function test_BooleanValue_enco
 //// Address.decode ////
 
 add_test(function test_Address_decode() {
   // Test for PLMN address
   wsp_decode_test(MMS.Address, strToCharCodeArray("+123.456-789/TYPE=PLMN"),
                       {address: "+123.456-789", type: "PLMN"});
   wsp_decode_test(MMS.Address, strToCharCodeArray("123456789/TYPE=PLMN"),
                       {address: "123456789", type: "PLMN"});
+
+  wsp_decode_test(MMS.Address, strToCharCodeArray("a23456789/TYPE=PLMN"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("++123456789/TYPE=PLMN"),
+                      null, "CodeError");
+
   // Test for IPv4
   wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.4/TYPE=IPv4"),
                       {address: "1.2.3.4", type: "IPv4"});
+
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.256/TYPE=IPv4"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.00/TYPE=IPv4"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3.a/TYPE=IPv4"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1.2.3/TYPE=IPv4"),
+                      null, "CodeError");
+
   // Test for IPv6
   wsp_decode_test(MMS.Address,
     strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6"),
     {address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"}
   );
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6:7:8/TYPE=IPv6"),
+                      {address: "1:2:3:4:5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6:7::/TYPE=IPv6"),
+                      {address: "1:2:3:4:5:6:7::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6::/TYPE=IPv6"),
+                      {address: "1:2:3:4:5:6::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5::/TYPE=IPv6"),
+                      {address: "1:2:3:4:5::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::/TYPE=IPv6"),
+                      {address: "1:2:3:4::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::/TYPE=IPv6"),
+                      {address: "1:2:3::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::/TYPE=IPv6"),
+                      {address: "1:2::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::/TYPE=IPv6"),
+                      {address: "1::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::/TYPE=IPv6"),
+                      {address: "::", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5:6::8/TYPE=IPv6"),
+                      {address: "1:2:3:4:5:6::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5::8/TYPE=IPv6"),
+                      {address: "1:2:3:4:5::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::8/TYPE=IPv6"),
+                      {address: "1:2:3:4::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::8/TYPE=IPv6"),
+                      {address: "1:2:3::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::8/TYPE=IPv6"),
+                      {address: "1:2::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::8/TYPE=IPv6"),
+                      {address: "1::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::8/TYPE=IPv6"),
+                      {address: "::8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4:5::7:8/TYPE=IPv6"),
+                      {address: "1:2:3:4:5::7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::7:8/TYPE=IPv6"),
+                      {address: "1:2:3:4::7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::7:8/TYPE=IPv6"),
+                      {address: "1:2:3::7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::7:8/TYPE=IPv6"),
+                      {address: "1:2::7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::7:8/TYPE=IPv6"),
+                      {address: "1::7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::7:8/TYPE=IPv6"),
+                      {address: "::7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3:4::6:7:8/TYPE=IPv6"),
+                      {address: "1:2:3:4::6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::6:7:8/TYPE=IPv6"),
+                      {address: "1:2:3::6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::6:7:8/TYPE=IPv6"),
+                      {address: "1:2::6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::6:7:8/TYPE=IPv6"),
+                      {address: "1::6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::6:7:8/TYPE=IPv6"),
+                      {address: "::6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2:3::5:6:7:8/TYPE=IPv6"),
+                      {address: "1:2:3::5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::5:6:7:8/TYPE=IPv6"),
+                      {address: "1:2::5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::5:6:7:8/TYPE=IPv6"),
+                      {address: "1::5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::5:6:7:8/TYPE=IPv6"),
+                      {address: "::5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:2::4:5:6:7:8/TYPE=IPv6"),
+                      {address: "1:2::4:5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::4:5:6:7:8/TYPE=IPv6"),
+                      {address: "1::4:5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::4:5:6:7:8/TYPE=IPv6"),
+                      {address: "::4:5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::3:4:5:6:7:8/TYPE=IPv6"),
+                      {address: "1::3:4:5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::3:4:5:6:7:8/TYPE=IPv6"),
+                      {address: "::3:4:5:6:7:8", type: "IPv6"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("::2:3:4:5:6:7:8/TYPE=IPv6"),
+                      {address: "::2:3:4:5:6:7:8", type: "IPv6"});
+
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1:g:3:4:5:6:7:8/TYPE=IPv6"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1::3:4::6:7:8/TYPE=IPv6"),
+                      null, "CodeError");
+
   // Test for other device-address
-  wsp_decode_test(MMS.Address, strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_"),
-                      {address: "+H-e.l%l_o", type: "W0r1d_"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("+H-e.1%l_o/TYPE=W0r1d_"),
+                      {address: "+H-e.1%l_o", type: "W0r1d_"});
+
+  wsp_decode_test(MMS.Address, strToCharCodeArray("addr/TYPE=type!"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("addr!/TYPE=type"),
+                      null, "CodeError");
+
   // Test for num-shortcode
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1"),
+                      {address: "1", type: "num"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("123"),
+                      {address: "123", type: "num"});
   wsp_decode_test(MMS.Address, strToCharCodeArray("+123"),
                       {address: "+123", type: "num"});
   wsp_decode_test(MMS.Address, strToCharCodeArray("*123"),
                       {address: "*123", type: "num"});
   wsp_decode_test(MMS.Address, strToCharCodeArray("#123"),
                       {address: "#123", type: "num"});
+
+  wsp_decode_test(MMS.Address, strToCharCodeArray("++123"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("!123"),
+                      null, "CodeError");
+  wsp_decode_test(MMS.Address, strToCharCodeArray("1*23"),
+                      null, "CodeError");
+
   // Test for alphanum-shortcode
-  wsp_decode_test(MMS.Address, strToCharCodeArray("H0wD0Y0uTurnTh1s0n"),
-                      {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("a"),
+                      {address: "a", type: "alphanum"});
+  wsp_decode_test(MMS.Address, strToCharCodeArray("H0w_D0_Y0u_Turn_Th1s_0n"),
+                      {address: "H0w_D0_Y0u_Turn_Th1s_0n", type: "alphanum"});
+
+  wsp_decode_test(MMS.Address, strToCharCodeArray("abc#"),
+                      null, "CodeError");
+
   // Test for email address
   wsp_decode_test(MMS.Address, strToCharCodeArray("Joe User <joe@user.org>"),
                       {address: "Joe User <joe@user.org>", type: "email"});
+  wsp_decode_test(MMS.Address,
+    strToCharCodeArray("a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-"),
+    {address: "a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-", type: "email"}
+  );
+
   // Test for invalid address
   wsp_decode_test(MMS.Address, strToCharCodeArray("@@@@@"),
                   null, "CodeError");
 
   run_next_test();
 });
 
 //// Address.encode ////
 
 add_test(function test_Address_encode() {
   // Test for PLMN address
   wsp_encode_test(MMS.Address, {address: "+123.456-789", type: "PLMN"},
                   strToCharCodeArray("+123.456-789/TYPE=PLMN"));
   wsp_encode_test(MMS.Address, {address: "123456789", type: "PLMN"},
                   strToCharCodeArray("123456789/TYPE=PLMN"));
+
+  wsp_encode_test(MMS.Address, {address: "a23456789", type: "PLMN"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "++123456789", type: "PLMN"},
+                  null, "CodeError");
+
   // Test for IPv4
   wsp_encode_test(MMS.Address, {address: "1.2.3.4", type: "IPv4"},
                   strToCharCodeArray("1.2.3.4/TYPE=IPv4"));
+
+  wsp_encode_test(MMS.Address, {address: "1.2.3.256", type: "IPv4"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "1.2.3.00", type: "IPv4"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "1.2.3.a", type: "IPv4"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "1.2.3", type: "IPv4"},
+                  null, "CodeError");
+
   // Test for IPv6
   wsp_encode_test(MMS.Address,
     {address: "1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000", type: "IPv6"},
     strToCharCodeArray("1111:AAAA:bbbb:CdEf:1ABC:2cde:3Def:0000/TYPE=IPv6")
   );
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6:7::", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5:6:7::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6::", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5:6::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5::", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4::", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3::", type: "IPv6"},
+                  strToCharCodeArray("1:2:3::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2::", type: "IPv6"},
+                  strToCharCodeArray("1:2::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::", type: "IPv6"},
+                  strToCharCodeArray("1::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::", type: "IPv6"},
+                  strToCharCodeArray("::/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5:6::8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5:6::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5::8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4::8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3::8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2::8", type: "IPv6"},
+                  strToCharCodeArray("1:2::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::8", type: "IPv6"},
+                  strToCharCodeArray("1::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::8", type: "IPv6"},
+                  strToCharCodeArray("::8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4:5::7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4:5::7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4::7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4::7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3::7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3::7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2::7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2::7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::7:8", type: "IPv6"},
+                  strToCharCodeArray("1::7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::7:8", type: "IPv6"},
+                  strToCharCodeArray("::7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3:4::6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3:4::6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3::6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3::6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2::6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2::6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1::6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::6:7:8", type: "IPv6"},
+                  strToCharCodeArray("::6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2:3::5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2:3::5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2::5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2::5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1::5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("::5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1:2::4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1:2::4:5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1::4:5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("::4:5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "1::3:4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("1::3:4:5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::3:4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("::3:4:5:6:7:8/TYPE=IPv6"));
+  wsp_encode_test(MMS.Address, {address: "::2:3:4:5:6:7:8", type: "IPv6"},
+                  strToCharCodeArray("::2:3:4:5:6:7:8/TYPE=IPv6"));
+
+  wsp_encode_test(MMS.Address, {address: "1:g:3:4:5:6:7:8", type: "IPv6"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "1::3:4:5:6::8", type: "IPv6"},
+                  null, "CodeError");
+
   // Test for other device-address
-  wsp_encode_test(MMS.Address, {address: "+H-e.l%l_o", type: "W0r1d_"},
-                  strToCharCodeArray("+H-e.l%l_o/TYPE=W0r1d_"));
+  wsp_encode_test(MMS.Address, {address: "+H-e.1%l_o", type: "W0r1d_"},
+                  strToCharCodeArray("+H-e.1%l_o/TYPE=W0r1d_"));
+
+  wsp_encode_test(MMS.Address, {address: "addr!", type: "type"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "addr", type: "type!"},
+                  null, "CodeError");
+
   // Test for num-shortcode
+  wsp_encode_test(MMS.Address, {address: "1", type: "num"},
+                  strToCharCodeArray("1"));
+  wsp_encode_test(MMS.Address, {address: "123", type: "num"},
+                  strToCharCodeArray("123"));
   wsp_encode_test(MMS.Address, {address: "+123", type: "num"},
                   strToCharCodeArray("+123"));
   wsp_encode_test(MMS.Address, {address: "*123", type: "num"},
                   strToCharCodeArray("*123"));
   wsp_encode_test(MMS.Address, {address: "#123", type: "num"},
                   strToCharCodeArray("#123"));
+
+  wsp_encode_test(MMS.Address, {address: "++123", type: "num"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "!123", type: "num"},
+                  null, "CodeError");
+  wsp_encode_test(MMS.Address, {address: "1#23", type: "num"},
+                  null, "CodeError");
+
   // Test for alphanum-shortcode
-  wsp_encode_test(MMS.Address, {address: "H0wD0Y0uTurnTh1s0n", type: "alphanum"},
-                  strToCharCodeArray("H0wD0Y0uTurnTh1s0n"));
+  wsp_encode_test(MMS.Address, {address: "a", type: "alphanum"},
+                  strToCharCodeArray("a"));
+  wsp_encode_test(MMS.Address, {address: "1", type: "alphanum"},
+                  strToCharCodeArray("1"));
+  wsp_encode_test(MMS.Address, {address: "H0w_D0_Y0u_Turn_Th1s_0n", type: "alphanum"},
+                  strToCharCodeArray("H0w_D0_Y0u_Turn_Th1s_0n"));
+
+  wsp_encode_test(MMS.Address, {address: "abc#", type: "alphanum"},
+                  null, "CodeError");
+
   // Test for email address
   wsp_encode_test(MMS.Address, {address: "Joe User <joe@user.org>", type: "email"},
                   strToCharCodeArray("Joe User <joe@user.org>"));
+  wsp_encode_test(MMS.Address,
+    {address: "a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-", type: "email"},
+    strToCharCodeArray("a-z.A-.Z.0-9!#$.%&.'*+./=?^._`{|}~-@a-.zA-Z.0-9!.#$%&'.*+/=?.^_`.{|}~-")
+  );
+
+  // Test for invalid address
+  wsp_encode_test(MMS.Address, {address: "a"}, null, "CodeError");
+  wsp_encode_test(MMS.Address, {type: "alphanum"}, null, "CodeError");
+
+  run_next_test();
+});
+
+//// Address.resolveType ////
+
+add_test(function test_Address_encode() {
+  function test(address, type) {
+    do_check_eq(MMS.Address.resolveType(address), type);
+  }
+
+  // Test ambiguous addresses.
+  test("+15525225554",      "PLMN");
+  test("5525225554",        "PLMN");
+  test("jkalbcjklg",        "PLMN");
+  test("jk.alb.cjk.lg",     "PLMN");
+  test("j:k:a:l:b:c:jk:lg", "PLMN");
+  test("55.252.255.54",     "IPv4");
+  test("5:5:2:5:2:2:55:54", "IPv6");
+  test("jk@alb.cjk.lg",     "email");
+  // Test empty address.  This is for received anonymous MMS messages.
+  test("",                  "Others");
 
   run_next_test();
 });
 
 //
 // Test target: HeaderField
 //
 
--- a/mobile/android/base/toolbar/ToolbarEditText.java
+++ b/mobile/android/base/toolbar/ToolbarEditText.java
@@ -6,98 +6,71 @@
 package org.mozilla.gecko.toolbar;
 
 import org.mozilla.gecko.R;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnCommitListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnDismissListener;
 import org.mozilla.gecko.toolbar.BrowserToolbar.OnFilterListener;
 import org.mozilla.gecko.CustomEditText;
 import org.mozilla.gecko.CustomEditText.OnKeyPreImeListener;
-import org.mozilla.gecko.InputMethods;
 import org.mozilla.gecko.util.GamepadUtils;
 import org.mozilla.gecko.util.StringUtils;
 
 import android.content.Context;
 import android.graphics.Rect;
 import android.text.Editable;
-import android.text.InputType;
 import android.text.Spanned;
 import android.text.TextUtils;
 import android.text.TextWatcher;
 import android.util.AttributeSet;
 import android.util.Log;
 import android.view.KeyEvent;
 import android.view.View;
 import android.view.View.OnKeyListener;
-import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputMethodManager;
 
 /**
 * {@code ToolbarEditText} is the text entry used when the toolbar
-* is in edit state. It handles all the necessary input method machinery
-* as well as the tracking of different text types (empty, search, or url).
+* is in edit state. It handles all the necessary input method machinery.
 * It's meant to be owned by {@code ToolbarEditLayout}.
 */
 public class ToolbarEditText extends CustomEditText
                              implements AutocompleteHandler {
 
     private static final String LOGTAG = "GeckoToolbarEditText";
 
-    // Used to track the current type of content in the
-    // text entry so that ToolbarEditLayout can update its
-    // state accordingly.
-    enum TextType {
-        EMPTY,
-        SEARCH_QUERY,
-        URL
-    }
-
-    interface OnTextTypeChangeListener {
-        public void onTextTypeChange(ToolbarEditText editText, TextType textType);
-    }
-
     private final Context mContext;
 
-    // Type of the URL bar go/search button
-    private TextType mToolbarTextType;
-
     private OnCommitListener mCommitListener;
     private OnDismissListener mDismissListener;
     private OnFilterListener mFilterListener;
-    private OnTextTypeChangeListener mTextTypeListener;
 
     // The previous autocomplete result returned to us
     private String mAutoCompleteResult = "";
 
     // The user typed part of the autocomplete result
     private String mAutoCompletePrefix = null;
 
     public ToolbarEditText(Context context, AttributeSet attrs) {
         super(context, attrs);
         mContext = context;
-
-        mToolbarTextType = TextType.EMPTY;
     }
 
     void setOnCommitListener(OnCommitListener listener) {
         mCommitListener = listener;
     }
 
     void setOnDismissListener(OnDismissListener listener) {
         mDismissListener = listener;
     }
 
     void setOnFilterListener(OnFilterListener listener) {
         mFilterListener = listener;
     }
 
-    void setOnTextTypeChangeListener(OnTextTypeChangeListener listener) {
-        mTextTypeListener = listener;
-    }
-
     @Override
     public void onAttachedToWindow() {
         setOnKeyListener(new KeyListener());
         setOnKeyPreImeListener(new KeyPreImeListener());
         addTextChangedListener(new TextChangeListener());
     }
 
     @Override
@@ -139,22 +112,16 @@ public class ToolbarEditText extends Cus
             return;
         }
 
         mAutoCompleteResult = result;
         getText().append(result.substring(text.length()));
         setSelection(text.length(), result.length());
     }
 
-    @Override
-    public void setEnabled(boolean enabled) {
-        super.setEnabled(enabled);
-        updateTextTypeFromText(getText().toString());
-    }
-
     private void resetAutocompleteState() {
         mAutoCompleteResult = "";
         mAutoCompletePrefix = null;
     }
 
     private static boolean hasCompositionString(Editable content) {
         Object[] spans = content.getSpans(0, content.length(), Object.class);
 
@@ -165,39 +132,16 @@ public class ToolbarEditText extends Cus
                     return true;
                 }
             }
         }
 
         return false;
     }
 
-    private void setTextType(TextType textType) {
-        mToolbarTextType = textType;
-
-        if (mTextTypeListener != null) {
-            mTextTypeListener.onTextTypeChange(this, textType);
-        }
-    }
-
-    private void updateTextTypeFromText(String text) {
-        if (text.length() == 0) {
-            setTextType(TextType.EMPTY);
-            return;
-        }
-
-        final TextType newType;
-        if (StringUtils.isSearchQuery(text, mToolbarTextType == TextType.SEARCH_QUERY)) {
-            newType = TextType.SEARCH_QUERY;
-        } else {
-            newType = TextType.URL;
-        }
-        setTextType(newType);
-    }
-
     private class TextChangeListener implements TextWatcher {
         @Override
         public void afterTextChanged(final Editable s) {
             if (!isEnabled()) {
                 return;
             }
 
             final String text = s.toString();
@@ -227,18 +171,16 @@ public class ToolbarEditText extends Cus
                 }
 
                 mAutoCompletePrefix = text;
 
                 if (reuseAutocomplete) {
                     onAutocomplete(mAutoCompleteResult);
                 }
             }
-
-            updateTextTypeFromText(text);
         }
 
         @Override
         public void beforeTextChanged(CharSequence s, int start, int count,
                                       int after) {
             // do nothing
         }