Merge m-c to inbound

This commit is contained in:
Wes Kocher 2014-05-16 17:15:49 -07:00
commit e6779db575
33 changed files with 1887 additions and 449 deletions

View File

@ -19,7 +19,7 @@
<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"/>

View File

@ -17,7 +17,7 @@
</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"/>

View File

@ -15,7 +15,7 @@
<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"/>

View File

@ -19,7 +19,7 @@
<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"/>

View File

@ -18,7 +18,7 @@
<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"/>
@ -120,7 +120,7 @@
<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"/>

View File

@ -4,6 +4,6 @@
"remote": "",
"branch": ""
},
"revision": "7e5b2c297555985ac76b069361ff252fe176a018",
"revision": "896e4c28c82da3f07a1910be2187201e2045ffd7",
"repo_path": "/integration/gaia-central"
}

View File

@ -17,7 +17,7 @@
<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"/>

View File

@ -15,7 +15,7 @@
<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"/>

View File

@ -19,7 +19,7 @@
<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"/>

View File

@ -17,7 +17,7 @@
<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"/>

View File

@ -17,7 +17,7 @@
</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"/>

View File

@ -17,7 +17,7 @@
<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"/>

View File

@ -5,7 +5,7 @@
- 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;
]>

View File

@ -10,3 +10,9 @@ webide.jar:
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)

View File

@ -5,7 +5,7 @@
- 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;
]>

View File

@ -15,7 +15,7 @@ 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";

View File

@ -5,7 +5,7 @@
- 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;
]>

View File

@ -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)

View File

@ -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)

View File

@ -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']

View File

@ -6,7 +6,6 @@
PARALLEL_DIRS += [
'content',
'locales',
'themes',
]

View File

@ -422,7 +422,11 @@
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");
}
}

View File

@ -2855,7 +2855,7 @@ chatbox {
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;
}
@ -2865,7 +2865,12 @@ chatbox {
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;
@ -2883,6 +2888,14 @@ chatbox {
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;
}
@ -2893,7 +2906,11 @@ chatbox {
%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");
}
/**

View File

@ -144,7 +144,10 @@ this.Address = {
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]};
}
@ -153,10 +156,7 @@ this.Address = {
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");
@ -179,7 +179,7 @@ this.Address = {
let str;
switch (value.type) {
case "email":
if (value.address.indexOf("@") > 0) {
if (value.address.match(this.REGEXP_EMAIL)) {
str = value.address;
}
break;
@ -212,7 +212,7 @@ this.Address = {
if (value.type.match(this.REGEXP_ENCODE_CUSTOM_TYPE)
&& value.address.match(this.REGEXP_ENCODE_CUSTOM_ADDR)) {
str = value.address + "/TYPE=" + value.type;
}
}
break;
}
@ -234,11 +234,11 @@ this.Address = {
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";
}
@ -252,20 +252,53 @@ this.Address = {
};
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

View File

@ -2007,9 +2007,10 @@ MmsService.prototype = {
// |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);
@ -2023,8 +2024,10 @@ MmsService.prototype = {
"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});
}

View File

@ -22,8 +22,8 @@ const RIL_GETTHREADSCURSOR_CID =
const DEBUG = false;
const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
const DB_VERSION = 23;
const DB_VERSION = 22;
const MESSAGE_STORE_NAME = "sms";
const THREAD_STORE_NAME = "thread";
const PARTICIPANT_STORE_NAME = "participant";
@ -142,8 +142,12 @@ MobileMessageDB.prototype = {
let currentVersion = event.oldVersion;
function update(currentVersion) {
let next = update.bind(self, currentVersion + 1);
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");
@ -245,8 +249,8 @@ MobileMessageDB.prototype = {
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();
@ -701,8 +705,8 @@ MobileMessageDB.prototype = {
// 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],
@ -1072,9 +1076,9 @@ MobileMessageDB.prototype = {
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!");
@ -1408,6 +1412,209 @@ MobileMessageDB.prototype = {
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) ||
@ -1536,10 +1743,22 @@ MobileMessageDB.prototype = {
}
},
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 + ")");
}
@ -1559,7 +1778,7 @@ MobileMessageDB.prototype = {
allPossibleAddresses.push(parsedAddress.internationalNumber);
}
if (DEBUG) {
debug("findParticipantRecordByAddress: allPossibleAddresses = " +
debug("findParticipantRecordByPlmnAddress: allPossibleAddresses = " +
JSON.stringify(allPossibleAddresses));
}
@ -1572,7 +1791,7 @@ MobileMessageDB.prototype = {
// If we're lucky, return the fetched participant record.
if (participantRecord) {
if (DEBUG) {
debug("findParticipantRecordByAddress: got "
debug("findParticipantRecordByPlmnAddress: got "
+ JSON.stringify(participantRecord));
}
aCallback(participantRecord);
@ -1596,16 +1815,8 @@ MobileMessageDB.prototype = {
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;
}
@ -1628,7 +1839,7 @@ MobileMessageDB.prototype = {
}
if (DEBUG) {
debug("findParticipantRecordByAddress: match "
debug("findParticipantRecordByPlmnAddress: match "
+ JSON.stringify(cursor.value));
}
aCallback(participantRecord);
@ -1641,16 +1852,58 @@ MobileMessageDB.prototype = {
}).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;
}
@ -1662,17 +1915,17 @@ MobileMessageDB.prototype = {
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;
}
@ -1684,18 +1937,68 @@ MobileMessageDB.prototype = {
}) (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;
}
@ -1704,7 +2007,7 @@ MobileMessageDB.prototype = {
request.onsuccess = function(event) {
let threadRecord = event.target.result;
if (DEBUG) {
debug("findThreadRecordByParticipants: return "
debug("findThreadRecordByPlmnAddresses: return "
+ JSON.stringify(threadRecord));
}
aCallback(threadRecord, participantIds);
@ -1712,6 +2015,34 @@ MobileMessageDB.prototype = {
});
},
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) {
@ -1742,7 +2073,7 @@ MobileMessageDB.prototype = {
}, aStoreNames);
},
saveRecord: function(aMessageRecord, aAddresses, aCallback) {
saveRecord: function(aMessageRecord, aThreadParticipants, aCallback) {
if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord));
let self = this;
@ -1776,13 +2107,14 @@ MobileMessageDB.prototype = {
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) ||
@ -1792,7 +2124,7 @@ MobileMessageDB.prototype = {
aMessageRecord.delivery != DELIVERY_RECEIVED ||
!isReplaceTypePid) {
this.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aAddresses);
aThreadStore, aMessageRecord, aThreadParticipants);
return;
}
@ -1805,12 +2137,16 @@ MobileMessageDB.prototype = {
// 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;
}
@ -1821,7 +2157,7 @@ MobileMessageDB.prototype = {
let cursor = event.target.result;
if (!cursor) {
self.realSaveRecord(aTransaction, aMessageStore, aParticipantStore,
aThreadStore, aMessageRecord, aAddresses);
aThreadStore, aMessageRecord, aThreadParticipants);
return;
}
@ -1838,17 +2174,18 @@ MobileMessageDB.prototype = {
// 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;
@ -1930,7 +2267,9 @@ MobileMessageDB.prototype = {
threadRecord = {
participantIds: participantIds,
participantAddresses: aAddresses,
participantAddresses: aThreadParticipants.map(function(typedAddress) {
return typedAddress.address;
}),
lastMessageId: aMessageRecord.id,
lastTimestamp: timestamp,
lastMessageSubject: lastMessageSubject || null,
@ -2127,7 +2466,13 @@ MobileMessageDB.prototype = {
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,
@ -2207,13 +2552,19 @@ MobileMessageDB.prototype = {
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;
@ -2284,16 +2635,7 @@ MobileMessageDB.prototype = {
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;
@ -2322,13 +2664,16 @@ MobileMessageDB.prototype = {
// |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,
@ -3011,9 +3356,15 @@ let FilterSearcherHelper = {
}
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.

View File

@ -43,6 +43,8 @@ qemu = true
[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]

View File

@ -2,200 +2,100 @@
* 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);
const TEXT_1 = "Nice to meet you";
const TEXT_2 = "Nice to meet you, too";
let pendingEmulatorCmdCount = 0;
function sendSmsToEmulator(from, text) {
++pendingEmulatorCmdCount;
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();
startTestCommon(function testCaseMain() {
return Promise.resolve()
// 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"])))
.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.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();
}
}
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();
});
});
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();
});
tasks.run();

View File

@ -2,14 +2,15 @@
* 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";
@ -21,96 +22,113 @@ 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);
});

View File

@ -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));
});

View File

@ -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);
});

View File

@ -50,30 +50,155 @@ add_test(function test_Address_decode() {
{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");
@ -89,30 +214,182 @@ add_test(function test_Address_encode() {
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();
});

View File

@ -11,14 +11,12 @@ 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;
@ -27,13 +25,11 @@ 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
@ -41,28 +37,11 @@ public class ToolbarEditText extends CustomEditText
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 = "";
@ -73,8 +52,6 @@ public class ToolbarEditText extends CustomEditText
public ToolbarEditText(Context context, AttributeSet attrs) {
super(context, attrs);
mContext = context;
mToolbarTextType = TextType.EMPTY;
}
void setOnCommitListener(OnCommitListener listener) {
@ -89,10 +66,6 @@ public class ToolbarEditText extends CustomEditText
mFilterListener = listener;
}
void setOnTextTypeChangeListener(OnTextTypeChangeListener listener) {
mTextTypeListener = listener;
}
@Override
public void onAttachedToWindow() {
setOnKeyListener(new KeyListener());
@ -144,12 +117,6 @@ public class ToolbarEditText extends CustomEditText
setSelection(text.length(), result.length());
}
@Override
public void setEnabled(boolean enabled) {
super.setEnabled(enabled);
updateTextTypeFromText(getText().toString());
}
private void resetAutocompleteState() {
mAutoCompleteResult = "";
mAutoCompletePrefix = null;
@ -170,29 +137,6 @@ public class ToolbarEditText extends CustomEditText
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) {
@ -232,8 +176,6 @@ public class ToolbarEditText extends CustomEditText
onAutocomplete(mAutoCompleteResult);
}
}
updateTextTypeFromText(text);
}
@Override