diff --git a/b2g/config/emulator-ics/sources.xml b/b2g/config/emulator-ics/sources.xml index 5cb57a197c6..a5f5067bb13 100644 --- a/b2g/config/emulator-ics/sources.xml +++ b/b2g/config/emulator-ics/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/emulator-jb/sources.xml b/b2g/config/emulator-jb/sources.xml index 3b58324a38b..8b95afb7957 100644 --- a/b2g/config/emulator-jb/sources.xml +++ b/b2g/config/emulator-jb/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/emulator-kk/sources.xml b/b2g/config/emulator-kk/sources.xml index 04cf578e125..46bfd01cd60 100644 --- a/b2g/config/emulator-kk/sources.xml +++ b/b2g/config/emulator-kk/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/emulator/sources.xml b/b2g/config/emulator/sources.xml index 5cb57a197c6..a5f5067bb13 100644 --- a/b2g/config/emulator/sources.xml +++ b/b2g/config/emulator/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/flame/sources.xml b/b2g/config/flame/sources.xml index 2acd82307c7..10a1ffc9c92 100644 --- a/b2g/config/flame/sources.xml +++ b/b2g/config/flame/sources.xml @@ -18,7 +18,7 @@ - + @@ -120,7 +120,7 @@ - + diff --git a/b2g/config/gaia.json b/b2g/config/gaia.json index b008f5a7782..f4c61ba943f 100644 --- a/b2g/config/gaia.json +++ b/b2g/config/gaia.json @@ -4,6 +4,6 @@ "remote": "", "branch": "" }, - "revision": "7e5b2c297555985ac76b069361ff252fe176a018", + "revision": "896e4c28c82da3f07a1910be2187201e2045ffd7", "repo_path": "/integration/gaia-central" } diff --git a/b2g/config/hamachi/sources.xml b/b2g/config/hamachi/sources.xml index 7ea97d8d1d1..b8e425e5cd9 100644 --- a/b2g/config/hamachi/sources.xml +++ b/b2g/config/hamachi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/helix/sources.xml b/b2g/config/helix/sources.xml index 8bc6be28ac0..e00426cb1bf 100644 --- a/b2g/config/helix/sources.xml +++ b/b2g/config/helix/sources.xml @@ -15,7 +15,7 @@ - + diff --git a/b2g/config/inari/sources.xml b/b2g/config/inari/sources.xml index bb60d8acc49..2f0fbae49cc 100644 --- a/b2g/config/inari/sources.xml +++ b/b2g/config/inari/sources.xml @@ -19,7 +19,7 @@ - + diff --git a/b2g/config/leo/sources.xml b/b2g/config/leo/sources.xml index b40be21ab91..e3796d252f7 100644 --- a/b2g/config/leo/sources.xml +++ b/b2g/config/leo/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/mako/sources.xml b/b2g/config/mako/sources.xml index ea82de49412..3d0f60682bd 100644 --- a/b2g/config/mako/sources.xml +++ b/b2g/config/mako/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/b2g/config/wasabi/sources.xml b/b2g/config/wasabi/sources.xml index bb3f09f03bb..9ac90efe0a6 100644 --- a/b2g/config/wasabi/sources.xml +++ b/b2g/config/wasabi/sources.xml @@ -17,7 +17,7 @@ - + diff --git a/browser/devtools/webide/content/details.xhtml b/browser/devtools/webide/content/details.xhtml index 0396e4dc0a3..29223696a12 100644 --- a/browser/devtools/webide/content/details.xhtml +++ b/browser/devtools/webide/content/details.xhtml @@ -5,7 +5,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + %webideDTD; ]> diff --git a/browser/devtools/webide/content/jar.mn b/browser/devtools/webide/content/jar.mn index 53c7d5b2525..70442b40506 100644 --- a/browser/devtools/webide/content/jar.mn +++ b/browser/devtools/webide/content/jar.mn @@ -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) diff --git a/browser/devtools/webide/content/newapp.xul b/browser/devtools/webide/content/newapp.xul index 8749a4e6950..31919cf217e 100644 --- a/browser/devtools/webide/content/newapp.xul +++ b/browser/devtools/webide/content/newapp.xul @@ -5,7 +5,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + %webideDTD; ]> diff --git a/browser/devtools/webide/content/webide.js b/browser/devtools/webide/content/webide.js index 31396d83b29..51e7d9cfa2a 100644 --- a/browser/devtools/webide/content/webide.js +++ b/browser/devtools/webide/content/webide.js @@ -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"; diff --git a/browser/devtools/webide/content/webide.xul b/browser/devtools/webide/content/webide.xul index 99efc5295ee..052fdf97742 100644 --- a/browser/devtools/webide/content/webide.xul +++ b/browser/devtools/webide/content/webide.xul @@ -5,7 +5,7 @@ - file, You can obtain one at http://mozilla.org/MPL/2.0/. --> + %webideDTD; ]> diff --git a/browser/devtools/webide/locales/Makefile.in b/browser/devtools/webide/locales/Makefile.in deleted file mode 100644 index 82f614ecea2..00000000000 --- a/browser/devtools/webide/locales/Makefile.in +++ /dev/null @@ -1,5 +0,0 @@ -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -DEFINES += -DAB_CD=$(AB_CD) diff --git a/browser/devtools/webide/locales/jar.mn b/browser/devtools/webide/locales/jar.mn deleted file mode 100644 index dc98d15f6b9..00000000000 --- a/browser/devtools/webide/locales/jar.mn +++ /dev/null @@ -1,10 +0,0 @@ -#filter substitution -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - - -webide.jar: -% locale webide @AB_CD@ %locale/ - locale/webide.dtd (%webide.dtd) - locale/webide.properties (%webide.properties) diff --git a/browser/devtools/webide/locales/moz.build b/browser/devtools/webide/locales/moz.build deleted file mode 100644 index c97072bba2d..00000000000 --- a/browser/devtools/webide/locales/moz.build +++ /dev/null @@ -1,7 +0,0 @@ -# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- -# vim: set filetype=python: -# This Source Code Form is subject to the terms of the Mozilla Public -# License, v. 2.0. If a copy of the MPL was not distributed with this -# file, You can obtain one at http://mozilla.org/MPL/2.0/. - -JAR_MANIFESTS += ['jar.mn'] \ No newline at end of file diff --git a/browser/devtools/webide/moz.build b/browser/devtools/webide/moz.build index 89146c9212d..aa25425be48 100644 --- a/browser/devtools/webide/moz.build +++ b/browser/devtools/webide/moz.build @@ -6,7 +6,6 @@ PARALLEL_DIRS += [ 'content', - 'locales', 'themes', ] diff --git a/browser/themes/windows/browser-aero.css b/browser/themes/windows/browser-aero.css index 86188a52a9b..b7ee8c9b524 100644 --- a/browser/themes/windows/browser-aero.css +++ b/browser/themes/windows/browser-aero.css @@ -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"); } } diff --git a/browser/themes/windows/browser.css b/browser/themes/windows/browser.css index a0eabd01b80..cc96a9f6668 100644 --- a/browser/themes/windows/browser.css +++ b/browser/themes/windows/browser.css @@ -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"); } /** diff --git a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm index f41774e735e..ba27c9c8279 100644 --- a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm +++ b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm @@ -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 diff --git a/dom/mobilemessage/src/gonk/MmsService.js b/dom/mobilemessage/src/gonk/MmsService.js index ca893d49cde..4e1106f4136 100644 --- a/dom/mobilemessage/src/gonk/MmsService.js +++ b/dom/mobilemessage/src/gonk/MmsService.js @@ -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}); } diff --git a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm index 5536b4ba4d2..2322e1646df 100644 --- a/dom/mobilemessage/src/gonk/MobileMessageDB.jsm +++ b/dom/mobilemessage/src/gonk/MobileMessageDB.jsm @@ -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. diff --git a/dom/mobilemessage/tests/marionette/manifest.ini b/dom/mobilemessage/tests/marionette/manifest.ini index 15e50d2ed8a..e7f3dc38056 100644 --- a/dom/mobilemessage/tests/marionette/manifest.ini +++ b/dom/mobilemessage/tests/marionette/manifest.ini @@ -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] diff --git a/dom/mobilemessage/tests/marionette/test_filter_number.js b/dom/mobilemessage/tests/marionette/test_filter_number.js index c71a8c3b11a..9d37dc5536e 100644 --- a/dom/mobilemessage/tests/marionette/test_filter_number.js +++ b/dom/mobilemessage/tests/marionette/test_filter_number.js @@ -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(); diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js b/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js index 75e585b3ac7..c11633b5eda 100644 --- a/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js +++ b/dom/mobilemessage/tests/marionette/test_mmdb_setmessagedeliverybyid_sms.js @@ -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 [, , - * , ]. - */ -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); }); diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js new file mode 100644 index 00000000000..e9582b6a1e4 --- /dev/null +++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_22.js @@ -0,0 +1,737 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'mmdb_head.js'; + +Cu.import("resource://gre/modules/PhoneNumberUtils.jsm"); + +let RIL = {}; +Cu.import("resource://gre/modules/ril_consts.js", RIL); + +let MMS = {}; +Cu.import("resource://gre/modules/MmsPduHelper.jsm", MMS); + +const DBNAME = "test_mmdb_upgradeSchema_22:" + newUUID(); + +const MESSAGE_STORE_NAME = "sms"; +const THREAD_STORE_NAME = "thread"; +const PARTICIPANT_STORE_NAME = "participant"; + +const DEBUG = false; + +const READ_WRITE = "readwrite"; + +const DELIVERY_SENDING = "sending"; +const DELIVERY_SENT = "sent"; +const DELIVERY_RECEIVED = "received"; +const DELIVERY_NOT_DOWNLOADED = "not-downloaded"; +const DELIVERY_ERROR = "error"; + +const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable"; +const DELIVERY_STATUS_SUCCESS = "success"; +const DELIVERY_STATUS_PENDING = "pending"; +const DELIVERY_STATUS_ERROR = "error"; + +const MESSAGE_CLASS_NORMAL = "normal"; + +const FILTER_READ_UNREAD = 0; +const FILTER_READ_READ = 1; + +const DISABLE_MMS_GROUPING_FOR_RECEIVING = true; + +let LEGACY = { + saveRecord: function(aMessageRecord, aAddresses, aCallback) { + if (DEBUG) debug("Going to store " + JSON.stringify(aMessageRecord)); + + let self = this; + this.newTxn(READ_WRITE, function(error, txn, stores) { + let notifyResult = function(aRv, aMessageRecord) { + if (!aCallback) { + return; + } + let domMessage = + aMessageRecord && self.createDomMessageFromRecord(aMessageRecord); + aCallback.notify(aRv, domMessage); + }; + + if (error) { + notifyResult(error, null); + return; + } + + txn.oncomplete = function oncomplete(event) { + if (aMessageRecord.id > self.lastMessageId) { + self.lastMessageId = aMessageRecord.id; + } + notifyResult(Cr.NS_OK, aMessageRecord); + }; + txn.onabort = function onabort(event) { + // TODO bug 832140 check event.target.errorCode + notifyResult(Cr.NS_ERROR_FAILURE, null); + }; + + let messageStore = stores[0]; + let participantStore = stores[1]; + let threadStore = stores[2]; + LEGACY.replaceShortMessageOnSave.call(self, txn, messageStore, + participantStore, threadStore, + aMessageRecord, aAddresses); + }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]); + }, + + replaceShortMessageOnSave: function(aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses) { + let isReplaceTypePid = (aMessageRecord.pid) && + ((aMessageRecord.pid >= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_1 && + aMessageRecord.pid <= RIL.PDU_PID_REPLACE_SHORT_MESSAGE_TYPE_7) || + aMessageRecord.pid == RIL.PDU_PID_RETURN_CALL_MESSAGE); + + if (aMessageRecord.type != "sms" || + aMessageRecord.delivery != DELIVERY_RECEIVED || + !isReplaceTypePid) { + LEGACY.realSaveRecord.call(this, aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses); + return; + } + + // 3GPP TS 23.040 subclause 9.2.3.9 "TP-Protocol-Identifier (TP-PID)": + // + // ... the MS shall check the originating address and replace any + // existing stored message having the same Protocol Identifier code + // and originating address with the new short message and other + // parameter values. If there is no message to be replaced, the MS + // shall store the message in the normal way. ... it is recommended + // that the SC address should not be checked by the MS." + let self = this; + this.findParticipantRecordByPlmnAddress(aParticipantStore, + aMessageRecord.sender, false, + function(participantRecord) { + if (!participantRecord) { + LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses); + return; + } + + let participantId = participantRecord.id; + let range = IDBKeyRange.bound([participantId, 0], [participantId, ""]); + let request = aMessageStore.index("participantIds").openCursor(range); + request.onsuccess = function onsuccess(event) { + let cursor = event.target.result; + if (!cursor) { + LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses); + return; + } + + // A message record with same participantId found. + // Verify matching criteria. + let foundMessageRecord = cursor.value; + if (foundMessageRecord.type != "sms" || + foundMessageRecord.sender != aMessageRecord.sender || + foundMessageRecord.pid != aMessageRecord.pid) { + cursor.continue(); + return; + } + + // Match! Now replace that found message record with current one. + aMessageRecord.id = foundMessageRecord.id; + LEGACY.realSaveRecord.call(self, aTransaction, aMessageStore, + aParticipantStore, aThreadStore, + aMessageRecord, aAddresses); + }; + }); + }, + + realSaveRecord: function(aTransaction, aMessageStore, aParticipantStore, + aThreadStore, aMessageRecord, aAddresses) { + let self = this; + this.findThreadRecordByPlmnAddresses(aThreadStore, aParticipantStore, + aAddresses, true, + function(threadRecord, participantIds) { + if (!participantIds) { + aTransaction.abort(); + return; + } + + let isOverriding = (aMessageRecord.id !== undefined); + if (!isOverriding) { + // |self.lastMessageId| is only updated in |txn.oncomplete|. + aMessageRecord.id = self.lastMessageId + 1; + } + + let timestamp = aMessageRecord.timestamp; + let insertMessageRecord = function(threadId) { + // Setup threadId & threadIdIndex. + aMessageRecord.threadId = threadId; + aMessageRecord.threadIdIndex = [threadId, timestamp]; + // Setup participantIdsIndex. + aMessageRecord.participantIdsIndex = []; + for each (let id in participantIds) { + aMessageRecord.participantIdsIndex.push([id, timestamp]); + } + + if (!isOverriding) { + // Really add to message store. + aMessageStore.put(aMessageRecord); + return; + } + + // If we're going to override an old message, we need to update the + // info of the original thread containing the overridden message. + // To get the original thread ID and read status of the overridden + // message record, we need to retrieve it before overriding it. + aMessageStore.get(aMessageRecord.id).onsuccess = function(event) { + let oldMessageRecord = event.target.result; + aMessageStore.put(aMessageRecord); + if (oldMessageRecord) { + self.updateThreadByMessageChange(aMessageStore, + aThreadStore, + oldMessageRecord.threadId, + aMessageRecord.id, + oldMessageRecord.read); + } + }; + }; + + if (threadRecord) { + let needsUpdate = false; + + if (threadRecord.lastTimestamp <= timestamp) { + let lastMessageSubject; + if (aMessageRecord.type == "mms") { + lastMessageSubject = aMessageRecord.headers.subject; + } + threadRecord.lastMessageSubject = lastMessageSubject || null; + threadRecord.lastTimestamp = timestamp; + threadRecord.body = aMessageRecord.body; + threadRecord.lastMessageId = aMessageRecord.id; + threadRecord.lastMessageType = aMessageRecord.type; + needsUpdate = true; + } + + if (!aMessageRecord.read) { + threadRecord.unreadCount++; + needsUpdate = true; + } + + if (needsUpdate) { + aThreadStore.put(threadRecord); + } + + insertMessageRecord(threadRecord.id); + return; + } + + let lastMessageSubject; + if (aMessageRecord.type == "mms") { + lastMessageSubject = aMessageRecord.headers.subject; + } + + threadRecord = { + participantIds: participantIds, + participantAddresses: aAddresses, + lastMessageId: aMessageRecord.id, + lastTimestamp: timestamp, + lastMessageSubject: lastMessageSubject || null, + body: aMessageRecord.body, + unreadCount: aMessageRecord.read ? 0 : 1, + lastMessageType: aMessageRecord.type, + }; + aThreadStore.add(threadRecord).onsuccess = function(event) { + let threadId = event.target.result; + insertMessageRecord(threadId); + }; + }); + }, + + fillReceivedMmsThreadParticipants: function(aMessage, threadParticipants) { + let receivers = aMessage.receivers; + // If we don't want to disable the MMS grouping for receiving, we need to + // add the receivers (excluding the user's own number) to the participants + // for creating the thread. Some cases might be investigated as below: + // + // 1. receivers.length == 0 + // This usually happens when receiving an MMS notification indication + // which doesn't carry any receivers. + // 2. receivers.length == 1 + // If the receivers contain single phone number, we don't need to + // add it into participants because we know that number is our own. + // 3. receivers.length >= 2 + // If the receivers contain multiple phone numbers, we need to add all + // of them but not the user's own number into participants. + if (DISABLE_MMS_GROUPING_FOR_RECEIVING || receivers.length < 2) { + return; + } + let isSuccess = false; + let slicedReceivers = receivers.slice(); + if (aMessage.msisdn) { + let found = slicedReceivers.indexOf(aMessage.msisdn); + if (found !== -1) { + isSuccess = true; + slicedReceivers.splice(found, 1); + } + } + + if (!isSuccess) { + // For some SIMs we cannot retrieve the vaild MSISDN (i.e. the user's + // own phone number), so we cannot correcly exclude the user's own + // number from the receivers, thus wrongly building the thread index. + if (DEBUG) debug("Error! Cannot strip out user's own phone number!"); + } + + threadParticipants = threadParticipants.concat(slicedReceivers); + }, + + saveReceivedMessage: function(aMessage, aCallback) { + if ((aMessage.type != "sms" && aMessage.type != "mms") || + (aMessage.type == "sms" && (aMessage.messageClass == undefined || + aMessage.sender == undefined)) || + (aMessage.type == "mms" && (aMessage.delivery == undefined || + aMessage.deliveryStatus == undefined || + !Array.isArray(aMessage.receivers))) || + aMessage.timestamp == undefined) { + if (aCallback) { + aCallback.notify(Cr.NS_ERROR_FAILURE, null); + } + return; + } + + let threadParticipants; + if (aMessage.type == "mms") { + if (aMessage.headers.from) { + aMessage.sender = aMessage.headers.from.address; + } else { + aMessage.sender = "anonymous"; + } + + threadParticipants = [aMessage.sender]; + LEGACY.fillReceivedMmsThreadParticipants.call(this, aMessage, + threadParticipants); + } else { // SMS + threadParticipants = [aMessage.sender]; + } + + let timestamp = aMessage.timestamp; + + // Adding needed indexes and extra attributes for internal use. + // threadIdIndex & participantIdsIndex are filled in saveRecord(). + aMessage.readIndex = [FILTER_READ_UNREAD, timestamp]; + aMessage.read = FILTER_READ_UNREAD; + + // If |sentTimestamp| is not specified, use 0 as default. + if (aMessage.sentTimestamp == undefined) { + aMessage.sentTimestamp = 0; + } + + if (aMessage.type == "mms") { + aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"]; + aMessage.isReadReportSent = false; + + // As a receiver, we don't need to care about the delivery status of + // others, so we put a single element with self's phone number in the + // |deliveryInfo| array. + aMessage.deliveryInfo = [{ + receiver: aMessage.phoneNumber, + deliveryStatus: aMessage.deliveryStatus, + deliveryTimestamp: 0, + readStatus: MMS.DOM_READ_STATUS_NOT_APPLICABLE, + readTimestamp: 0, + }]; + + delete aMessage.deliveryStatus; + } + + if (aMessage.type == "sms") { + aMessage.delivery = DELIVERY_RECEIVED; + aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS; + aMessage.deliveryTimestamp = 0; + + if (aMessage.pid == undefined) { + aMessage.pid = RIL.PDU_PID_DEFAULT; + } + } + aMessage.deliveryIndex = [aMessage.delivery, timestamp]; + + LEGACY.saveRecord.call(this, aMessage, threadParticipants, aCallback); + }, + + saveSendingMessage: function(aMessage, aCallback) { + if ((aMessage.type != "sms" && aMessage.type != "mms") || + (aMessage.type == "sms" && aMessage.receiver == undefined) || + (aMessage.type == "mms" && !Array.isArray(aMessage.receivers)) || + aMessage.deliveryStatusRequested == undefined || + aMessage.timestamp == undefined) { + if (aCallback) { + aCallback.notify(Cr.NS_ERROR_FAILURE, null); + } + return; + } + + // Set |aMessage.deliveryStatus|. Note that for MMS record + // it must be an array of strings; For SMS, it's a string. + let deliveryStatus = aMessage.deliveryStatusRequested + ? DELIVERY_STATUS_PENDING + : DELIVERY_STATUS_NOT_APPLICABLE; + if (aMessage.type == "sms") { + aMessage.deliveryStatus = deliveryStatus; + // If |deliveryTimestamp| is not specified, use 0 as default. + if (aMessage.deliveryTimestamp == undefined) { + aMessage.deliveryTimestamp = 0; + } + } else if (aMessage.type == "mms") { + let receivers = aMessage.receivers + if (!Array.isArray(receivers)) { + if (DEBUG) { + debug("Need receivers for MMS. Fail to save the sending message."); + } + if (aCallback) { + aCallback.notify(Cr.NS_ERROR_FAILURE, null); + } + return; + } + let readStatus = aMessage.headers["x-mms-read-report"] + ? MMS.DOM_READ_STATUS_PENDING + : MMS.DOM_READ_STATUS_NOT_APPLICABLE; + aMessage.deliveryInfo = []; + for (let i = 0; i < receivers.length; i++) { + aMessage.deliveryInfo.push({ + receiver: receivers[i], + deliveryStatus: deliveryStatus, + deliveryTimestamp: 0, + readStatus: readStatus, + readTimestamp: 0, + }); + } + } + + let timestamp = aMessage.timestamp; + + // Adding needed indexes and extra attributes for internal use. + // threadIdIndex & participantIdsIndex are filled in saveRecord(). + aMessage.deliveryIndex = [DELIVERY_SENDING, timestamp]; + aMessage.readIndex = [FILTER_READ_READ, timestamp]; + aMessage.delivery = DELIVERY_SENDING; + aMessage.messageClass = MESSAGE_CLASS_NORMAL; + aMessage.read = FILTER_READ_READ; + + // |sentTimestamp| is not available when the message is still sedning. + aMessage.sentTimestamp = 0; + + let addresses; + if (aMessage.type == "sms") { + addresses = [aMessage.receiver]; + } else if (aMessage.type == "mms") { + addresses = aMessage.receivers; + } + LEGACY.saveRecord.call(this, aMessage, addresses, aCallback); + }, +}; + +function callMmdbMethodLegacy(aMmdb, aMethodName) { + let deferred = Promise.defer(); + + let args = Array.slice(arguments, 2); + args.push({ + notify: function(aRv) { + if (!Components.isSuccessCode(aRv)) { + ok(true, aMethodName + " returns a unsuccessful code: " + aRv); + deferred.reject(Array.slice(arguments)); + } else { + ok(true, aMethodName + " returns a successful code: " + aRv); + deferred.resolve(Array.slice(arguments)); + } + } + }); + LEGACY[aMethodName].apply(aMmdb, args); + + return deferred.promise; +} + +function saveSendingMessageLegacy(aMmdb, aMessage) { + return callMmdbMethodLegacy(aMmdb, "saveSendingMessage", aMessage); +} + +function saveReceivedMessageLegacy(aMmdb, aMessage) { + return callMmdbMethodLegacy(aMmdb, "saveReceivedMessage", aMessage); +} + +// Have a long long subject causes the send fails, so we don't need +// networking here. +const MMS_MAX_LENGTH_SUBJECT = 40; +function genMmsSubject(sep) { + return "Hello " + (new Array(MMS_MAX_LENGTH_SUBJECT).join(sep)) + " World!"; +} + +function generateMms(aSender, aReceivers, aDelivery) { + let message = { + headers: {}, + type: "mms", + timestamp: Date.now(), + receivers: aReceivers, + subject: genMmsSubject(' '), + attachments: [], + }; + + message.headers.subject = message.subject; + message.headers.to = []; + for (let i = 0; i < aReceivers.length; i++) { + let receiver = aReceivers[i]; + let entry = { type: MMS.Address.resolveType(receiver) }; + if (entry.type == "PLMN") { + entry.address = PhoneNumberUtils.normalize(receiver, false); + } else { + entry.address = receiver; + } + ok(true, "MMS to address '" + receiver +"' resolved as type " + entry.type); + message.headers.to.push(entry); + } + if (aSender) { + message.headers.from = { + address: aSender, + type: MMS.Address.resolveType(aSender) + }; + ok(true, "MMS from address '" + aSender +"' resolved as type " + + message.headers.from.type); + } + + if ((aDelivery === DELIVERY_RECEIVED) || + (aDelivery === DELIVERY_NOT_DOWNLOADED)) { + message.delivery = aDelivery; + message.deliveryStatus = DELIVERY_STATUS_SUCCESS; + } else { + message.deliveryStatusRequested = false; + } + + return message; +} + +function generateSms(aSender, aReceiver, aDelivery) { + let message = { + type: "sms", + sender: aSender, + timestamp: Date.now(), + receiver: aReceiver, + body: "The snow grows white on the mountain tonight.", + }; + + if (aDelivery === DELIVERY_RECEIVED) { + message.messageClass = MESSAGE_CLASS_NORMAL; + } else { + message.deliveryStatusRequested = false; + } + + return message; +} + +function matchArray(lhs, rhs) { + if (rhs.length != lhs.length) { + return false; + } + + for (let k = 0; k < lhs.length; k++) { + if (lhs[k] != rhs[k]) { + return false; + } + } + + return true; +} + +const TEST_ADDRESSES = [ + "+15525225554", // MMS, TYPE=PLMN + "5525225554", // MMS, TYPE=PLMN + "jkalbcjklg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer + "jk.alb.cjk.lg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer + "j:k:a:l:b:c:jk:lg", // MMS, TYPE=PLMN, because of PhoneNumberNormalizer + "55.252.255.54", // MMS, TYPE=IPv4 + "5:5:2:5:2:2:55:54", // MMS, TYPE=IPv6 + "jk@alb.cjk.lg", // MMS, TYPE=email + "___" // MMS, TYPE=Others +]; + +function populateDatabase(aMmdb) { + log("Populating database:"); + + let promise = Promise.resolve() + + // We're generating other messages that would be identified as the same + // participant with "+15525225554". + .then(() => saveReceivedMessageLegacy( + aMmdb, generateSms("+15525225554", null, DELIVERY_RECEIVED))) + + // SMS, national number. + .then(() => saveReceivedMessageLegacy( + aMmdb, generateSms("5525225554", null, DELIVERY_RECEIVED))); + + for (let i = 0; i < TEST_ADDRESSES.length; i++) { + let address = TEST_ADDRESSES[i]; + promise = promise.then(() => saveReceivedMessageLegacy( + aMmdb, generateMms(address, ["a"], DELIVERY_RECEIVED))); + } + + // Permutation of TEST_ADDRESSES. + for (let i = 0; i < TEST_ADDRESSES.length; i++) { + for (let j = i + 1; j < TEST_ADDRESSES.length; j++) { + let addr_i = TEST_ADDRESSES[i], addr_j = TEST_ADDRESSES[j]; + promise = promise.then(() => saveSendingMessageLegacy( + aMmdb, generateMms(null, [addr_i, addr_j], DELIVERY_SENDING))); + } + } + + // At this time, we have 3 threads, whose |participants| are: + // ["+15525225554"], ["___"], and ["+15525225554", "___"]. The number of each + // thread are [ (2 + 8 + 8 * 7 / 2), 1, 8 ] = [ 38, 1, 8 ]. + + return promise; +} + +function doVerifyDatabase(aMmdb, aExpected) { + // 1) retrieve all threads. + return createThreadCursor(aMmdb) + .then(function(aValues) { + let [errorCode, domThreads] = aValues; + is(errorCode, 0, "errorCode"); + is(domThreads.length, aExpected.length, "domThreads.length"); + + let numMessagesInThread = []; + let totalMessages = 0; + + for (let i = 0; i < domThreads.length; i++) { + let domThread = domThreads[i]; + log(" thread<" + domThread.id + "> : " + domThread.participants); + + let index = (function() { + let rhs = domThread.participants; + for (let j = 0; j < aExpected.length; j++) { + let lhs = aExpected[j].participants; + if (matchArray(lhs, rhs)) { + return j; + } + } + })(); + // 2) make sure all retrieved threads are in expected array. + ok(index >= 0, "validity of domThread.participants"); + + // 3) fill out numMessagesInThread, which is a => + // 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)); +}); diff --git a/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js new file mode 100644 index 00000000000..ff3a3288055 --- /dev/null +++ b/dom/mobilemessage/tests/marionette/test_mmdb_upgradeSchema_current_structure.js @@ -0,0 +1,171 @@ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ */ + +MARIONETTE_TIMEOUT = 60000; +MARIONETTE_HEAD_JS = 'mmdb_head.js'; + +const DBNAME = "test_mmdb_upgradeSchema_current_structure:" + newUUID(); + +const LAYOUT = { + sms: { + keyPath: "id", + autoIncrement: false, + indice: { + delivery: { + keyPath: "deliveryIndex", + multiEntry: false, + unique: false, + }, + + envelopeId: { + keyPath: "envelopeIdIndex", + multiEntry: false, + unique: true, + }, + + participantIds: { + keyPath: "participantIdsIndex", + multiEntry: true, + unique: false, + }, + + read: { + keyPath: "readIndex", + multiEntry: false, + unique: false, + }, + + threadId: { + keyPath: "threadIdIndex", + multiEntry: false, + unique: false, + }, + + timestamp: { + keyPath: "timestamp", + multiEntry: false, + unique: false, + }, + + transactionId: { + keyPath: "transactionIdIndex", + multiEntry: false, + unique: true, + } + }, + }, + + thread: { + keyPath: "id", + autoIncrement: true, + indice: { + lastTimestamp: { + keyPath: "lastTimestamp", + multiEntry: false, + unique: false, + }, + + participantIds: { + keyPath: "participantIds", + multiEntry: false, + unique: false, + } + }, + }, + + participant: { + keyPath: "id", + autoIncrement: true, + indice: { + addresses: { + keyPath: "addresses", + multiEntry: true, + unique: false, + } + }, + }, + + "sms-segment": { + keyPath: "id", + autoIncrement: true, + indice: { + hash: { + keyPath: "hash", + multiEntry: false, + unique: true, + } + }, + } +}; + +function verifyIndex(aIndex, aIndexLayout) { + log(" Verifying index '" + aIndex.name + "'"); + + is(aIndex.keyPath, aIndexLayout.keyPath, "aIndex.keyPath"); + is(aIndex.multiEntry, aIndexLayout.multiEntry, "aIndex.multiEntry"); + is(aIndex.unique, aIndexLayout.unique, "aIndex.unique"); +} + +function verifyStore(aObjectStore, aStoreLayout) { + log("Verifying object store '" + aObjectStore.name + "'"); + + is(aObjectStore.keyPath, aStoreLayout.keyPath, "aObjectStore.keyPath"); + is(aObjectStore.autoIncrement, aStoreLayout.autoIncrement, + "aObjectStore.autoIncrement"); + + let expectedIndexNames = Object.keys(aStoreLayout.indice); + for (let i = 0; i < aObjectStore.indexNames.length; i++) { + let indexName = aObjectStore.indexNames.item(i); + + let index = expectedIndexNames.indexOf(indexName); + ok(index >= 0, "Index name '" + indexName + "' validity"); + expectedIndexNames.splice(index, 1); + + verifyIndex(aObjectStore.index(indexName), aStoreLayout.indice[indexName]); + } + + // All index names should have been verified and leaves expectedIndexNames an + // empty array. + is(expectedIndexNames.length, 0, "Extra indice: " + expectedIndexNames); +} + +function verifyDatabase(aMmdb) { + let deferred = Promise.defer(); + + let expectedStoreNames = Object.keys(LAYOUT); + aMmdb.newTxn("readonly", function(aError, aTransaction, aObjectStores) { + if (!Array.isArray(aObjectStores)) { + // When we have only one object store open, aObjectStores is an instance + // of IDBObjectStore. Push it to an array for convenience here. + aObjectStores = [aObjectStores]; + } + + is(aObjectStores.length, expectedStoreNames.length, + "expected number of object stores"); + + let slicedStoreNames = expectedStoreNames.slice(); + for (let i = 0; i < aObjectStores.length; i++) { + let objectStore = aObjectStores[i]; + + let index = slicedStoreNames.indexOf(objectStore.name); + ok(index >= 0, "objectStore.name '" + objectStore.name + "' validity"); + slicedStoreNames.splice(index, 1); + + verifyStore(objectStore, LAYOUT[objectStore.name]); + } + + // All store names should have been verified and leaves slicedStoreNames an + // empty array. + is(slicedStoreNames.length, 0, "Extra object stores: " + slicedStoreNames); + + deferred.resolve(aMmdb); + }, expectedStoreNames); + + return deferred.promise; +} + +startTestBase(function testCaseMain() { + return initMobileMessageDB(newMobileMessageDB(), DBNAME, 0) + .then(verifyDatabase) + .then(closeMobileMessageDB); +}); diff --git a/dom/mobilemessage/tests/test_mms_pdu_helper.js b/dom/mobilemessage/tests/test_mms_pdu_helper.js index 1b1806da905..6f01e27eb23 100644 --- a/dom/mobilemessage/tests/test_mms_pdu_helper.js +++ b/dom/mobilemessage/tests/test_mms_pdu_helper.js @@ -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 "), {address: "Joe User ", 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 ", type: "email"}, strToCharCodeArray("Joe User ")); + 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(); }); diff --git a/mobile/android/base/toolbar/ToolbarEditText.java b/mobile/android/base/toolbar/ToolbarEditText.java index 30a8efd82eb..19b8627c32d 100644 --- a/mobile/android/base/toolbar/ToolbarEditText.java +++ b/mobile/android/base/toolbar/ToolbarEditText.java @@ -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