Bug 810091 - B2G MMS: Don't download message twice on receiving duplicated notification. r=vyang, r=gene.lian

This commit is contained in:
Chia-hung Tai 2013-03-22 19:04:02 +08:00
parent 56bd55c05b
commit 8309216d97
3 changed files with 239 additions and 114 deletions

View File

@ -946,6 +946,7 @@ MmsService.prototype = {
intermediate.deliveryStatus = [DELIVERY_STATUS_PENDING];
intermediate.timestamp = Date.now();
intermediate.sender = null;
intermediate.transactionId = intermediate.headers["x-mms-transaction-id"];
if (intermediate.headers.from) {
intermediate.sender = intermediate.headers.from.address;
} else {
@ -1047,6 +1048,147 @@ MmsService.prototype = {
});
},
/**
* A helper function to broadcast the mms sent system message and notify observers.
*
* @params aDomMessage
* The nsIDOMMozMmsMessage object.
*/
broadcastSentMessageEvent: function broadcastSentMessageEvent(aDomMessage) {
// Broadcasting a 'sms-sent' system message to open apps.
this.broadcastMmsSystemMessage("sms-sent", aDomMessage);
// Notifying observers an MMS message is sent.
Services.obs.notifyObservers(aDomMessage, kSmsSentObserverTopic, null);
},
/**
* A helper function to broadcast the mms received system message and notify observers.
*
* @params aDomMessage
* The nsIDOMMozMmsMessage object.
*/
broadcastReceivedMessageEvent :function broadcastReceivedMessageEvent(aDomMessage) {
// Broadcasting a 'sms-received' system message to open apps.
this.broadcastMmsSystemMessage("sms-received", aDomMessage);
// Notifying observers an MMS message is comming.
Services.obs.notifyObservers(aDomMessage, kSmsReceivedObserverTopic, null);
},
/**
* Callback for retrieveMessage.
*/
retrieveMessageCallback: function retrieveMessageCallback(wish,
savableMessage,
mmsStatus,
retrievedMessage) {
debug("retrievedMessage = " + JSON.stringify(retrievedMessage));
// The absence of the field does not indicate any default
// value. So we go check the same field in the retrieved
// message instead.
if (wish == null && retrievedMessage) {
wish = retrievedMessage.headers["x-mms-delivery-report"];
}
let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
wish);
let transactionId = retrievedMessage.headers["x-mms-transaction-id"];
// If the mmsStatus isn't MMS_PDU_STATUS_RETRIEVED after retrieving,
// something must be wrong with MMSC, so stop updating the DB record.
// We could send a message to content to notify the user the MMS
// retrieving failed. The end user has to retrieve the MMS again.
if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
let transaction = new NotifyResponseTransaction(transactionId,
mmsStatus,
reportAllowed);
transaction.run();
return;
}
savableMessage = this.mergeRetrievalConfirmation(retrievedMessage,
savableMessage);
gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
(function (rv, domMessage) {
let success = Components.isSuccessCode(rv);
let transaction =
new NotifyResponseTransaction(transactionId,
success ? MMS.MMS_PDU_STATUS_RETRIEVED
: MMS.MMS_PDU_STATUS_DEFERRED,
reportAllowed);
transaction.run();
if (!success) {
// At this point we could send a message to content to notify the user
// that storing an incoming MMS failed, most likely due to a full disk.
// The end user has to retrieve the MMS again.
debug("Could not store MMS " + domMessage.id +
", error code " + rv);
return;
}
this.broadcastReceivedMessageEvent(domMessage);
}).bind(this));
},
/**
* Callback for saveReceivedMessage.
*/
saveReceivedMessageCallback: function saveReceivedMessageCallback(savableMessage,
rv,
domMessage) {
let success = Components.isSuccessCode(rv);
if (!success) {
// At this point we could send a message to content to notify the
// user that storing an incoming MMS notification indication failed,
// ost likely due to a full disk.
debug("Could not store MMS " + JSON.stringify(savableMessage) +
", error code " + rv);
// Because MMSC will resend the notification indication once we don't
// response the notification. Hope the end user will clean some space
// for the resent notification indication.
return;
}
// For X-Mms-Report-Allowed and X-Mms-Transaction-Id
let wish = savableMessage.headers["x-mms-delivery-report"];
let transactionId = savableMessage.headers["x-mms-transaction-id"];
this.broadcastReceivedMessageEvent(domMessage);
let retrievalMode = RETRIEVAL_MODE_MANUAL;
try {
retrievalMode = Services.prefs.getCharPref(PREF_RETRIEVAL_MODE);
} catch (e) {}
let isRoaming = gMmsConnection.isDataConnRoaming();
if ((retrievalMode === RETRIEVAL_MODE_AUTOMATIC_HOME && isRoaming) ||
RETRIEVAL_MODE_MANUAL === retrievalMode ||
RETRIEVAL_MODE_NEVER === retrievalMode) {
let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode
? MMS.MMS_PDU_STATUS_REJECTED
: MMS.MMS_PDU_STATUS_DEFERRED;
// For X-Mms-Report-Allowed
let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
wish);
let transaction = new NotifyResponseTransaction(transactionId,
mmsStatus,
reportAllowed);
transaction.run();
return;
}
let url = savableMessage.headers["x-mms-content-location"].uri;
// For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not
// roaming, proceed to retrieve MMS.
this.retrieveMessage(url, this.retrieveMessageCallback.bind(this, wish, savableMessage));
},
/**
* Handle incoming M-Notification.ind PDU.
*
@ -1054,118 +1196,21 @@ MmsService.prototype = {
* The parsed MMS message object.
*/
handleNotificationIndication: function handleNotificationIndication(notification) {
let url = notification.headers["x-mms-content-location"].uri;
// TODO: bug 810091 - don't download message twice when receiving duplicated
// notification.
let transactionId = notification.headers["x-mms-transaction-id"];
// For X-Mms-Report-Allowed
let wish = notification.headers["x-mms-delivery-report"];
let savableMessage = this.convertIntermediateToSavable(notification);
gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
(function (rv, domMessage) {
let success = Components.isSuccessCode(rv);
if (!success) {
// At this point we could send a message to content to notify the
// user that storing an incoming MMS notification indication failed,
// ost likely due to a full disk.
debug("Could not store MMS " + JSON.stringify(savableMessage) +
", error code " + rv);
// Because MMSC will resend the notification indication once we don't
// response the notification. Hope the end user will clean some space
// for the resent notification indication.
gMobileMessageDatabaseService.getMessageRecordByTransactionId(transactionId,
(function (aRv, aMessageRecord) {
if (Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR === aRv
&& aMessageRecord) {
debug("We already got the NotificationIndication with transactionId = "
+ transactionId + " before.");
return;
}
// Broadcasting an 'sms-received' system message to open apps.
this.broadcastMmsSystemMessage("sms-received", domMessage);
let savableMessage = this.convertIntermediateToSavable(notification);
// Notifying observers a new notification indication is coming.
Services.obs.notifyObservers(domMessage, kSmsReceivedObserverTopic, null);
let retrievalMode = RETRIEVAL_MODE_MANUAL;
try {
retrievalMode = Services.prefs.getCharPref(PREF_RETRIEVAL_MODE);
} catch (e) {}
let isRoaming = gMmsConnection.isDataConnRoaming();
if ((retrievalMode === RETRIEVAL_MODE_AUTOMATIC_HOME && isRoaming) ||
RETRIEVAL_MODE_MANUAL === retrievalMode ||
RETRIEVAL_MODE_NEVER === retrievalMode) {
let mmsStatus = RETRIEVAL_MODE_NEVER === retrievalMode
? MMS.MMS_PDU_STATUS_REJECTED
: MMS.MMS_PDU_STATUS_DEFERRED;
// For X-Mms-Report-Allowed
let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
wish);
let transaction = new NotifyResponseTransaction(transactionId,
mmsStatus,
reportAllowed);
transaction.run();
return;
}
// For RETRIEVAL_MODE_AUTOMATIC or RETRIEVAL_MODE_AUTOMATIC_HOME but not
// roaming, proceed to retrieve MMS.
this.retrieveMessage(url, (function responseNotify(mmsStatus,
retrievedMessage) {
debug("retrievedMessage = " + JSON.stringify(retrievedMessage));
// The absence of the field does not indicate any default
// value. So we go check the same field in the retrieved
// message instead.
if (wish == null && retrievedMessage) {
wish = retrievedMessage.headers["x-mms-delivery-report"];
}
let reportAllowed = this.getReportAllowed(this.confSendDeliveryReport,
wish);
// If the mmsStatus isn't MMS_PDU_STATUS_RETRIEVED after retrieving,
// something must be wrong with MMSC, so stop updating the DB record.
// We could send a message to content to notify the user the MMS
// retrieving failed. The end user has to retrieve the MMS again.
if (MMS.MMS_PDU_STATUS_RETRIEVED !== mmsStatus) {
let transaction = new NotifyResponseTransaction(transactionId,
mmsStatus,
reportAllowed);
transaction.run();
return;
}
savableMessage = this.mergeRetrievalConfirmation(retrievedMessage,
savableMessage);
gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
(function (rv, domMessage) {
let success = Components.isSuccessCode(rv);
let transaction =
new NotifyResponseTransaction(transactionId,
success ? MMS.MMS_PDU_STATUS_RETRIEVED
: MMS.MMS_PDU_STATUS_DEFERRED,
reportAllowed);
transaction.run();
if (!success) {
// At this point we could send a message to content to
// notify the user that storing an incoming MMS failed,
// most likely due to a full disk. The end user has to
// retrieve the MMS again.
debug("Could not store MMS " + domMessage.id +
", error code " + rv);
return;
}
// Broadcasting an 'sms-received' system message to open apps.
this.broadcastMmsSystemMessage("sms-received", domMessage);
// Notifying observers an MMS message is received.
Services.obs.notifyObservers(domMessage, kSmsReceivedObserverTopic, null);
}).bind(this));
}).bind(this));
gMobileMessageDatabaseService
.saveReceivedMessage(savableMessage,
this.saveReceivedMessageCallback.bind(this, savableMessage));
}).bind(this));
},
@ -1307,9 +1352,8 @@ MmsService.prototype = {
return;
}
self.broadcastMmsSystemMessage("sms-sent", aDomMessage);
self.broadcastSentMessageEvent(domMessage);
aRequest.notifyMessageSent(aDomMessage);
Services.obs.notifyObservers(aDomMessage, kSmsSentObserverTopic, null);
});
};
@ -1411,9 +1455,7 @@ MmsService.prototype = {
}
// Notifying observers a new MMS message is retrieved.
aRequest.notifyMessageGot(domMessage);
// Broadcasting an 'sms-received' system message to open apps.
this.broadcastMmsSystemMessage("sms-received", domMessage);
Services.obs.notifyObservers(domMessage, kSmsReceivedObserverTopic, null);
this.broadcastReceivedMessageEvent(domMessage);
let transaction = new AcknowledgeTransaction(transactionId, reportAllowed);
transaction.run();
}).bind(this));

View File

@ -24,7 +24,7 @@ interface nsIRilMobileMessageDatabaseRecordCallback : nsISupports
void notify(in nsresult aRv, in jsval aMessageRecord);
};
[scriptable, uuid(8f49216f-bc0c-420e-b77e-7f1cbdcd245f)]
[scriptable, uuid(0ead3154-542d-4e2c-a624-9e3cec504758)]
interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
{
/**
@ -40,6 +40,7 @@ interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
* - |delivery| DOMString: the delivery state of received message
* - |deliveryStatus| DOMString Array: the delivery status of received message
* - |receivers| DOMString Array: the phone numbers of receivers
* - |transactionId| DOMString: the transaction ID from MMS pdu header.
*
* Note: |deliveryStatus| should only contain single string to specify
* the delivery status of MMS message for the phone owner self.
@ -82,4 +83,12 @@ interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
*/
void getMessageRecordById(in long aMessageId,
in nsIRilMobileMessageDatabaseRecordCallback aCallback);
/**
* |aTransactionId| DOMString: the transaction ID of MMS pdu.
* |aCallback| nsIRilMobileMessageDatabaseCallback: a callback which takes
* result flag and message record as parameters.
*/
void getMessageRecordByTransactionId(in DOMString aTransactionId,
in nsIRilMobileMessageDatabaseRecordCallback aCallback);
};

View File

@ -21,7 +21,7 @@ const RIL_GETTHREADSCURSOR_CID =
const DEBUG = false;
const DB_NAME = "sms";
const DB_VERSION = 8;
const DB_VERSION = 9;
const MESSAGE_STORE_NAME = "sms";
const THREAD_STORE_NAME = "thread";
const PARTICIPANT_STORE_NAME = "participant";
@ -30,6 +30,7 @@ const MOST_RECENT_STORE_NAME = "most-recent";
const DELIVERY_SENDING = "sending";
const DELIVERY_SENT = "sent";
const DELIVERY_RECEIVED = "received";
const DELIVERY_NOT_DOWNLOADED = "not-downloaded";
const DELIVERY_STATUS_NOT_APPLICABLE = "not-applicable";
const DELIVERY_STATUS_SUCCESS = "success";
@ -200,6 +201,10 @@ MobileMessageDatabaseService.prototype = {
if (DEBUG) debug("Upgrade to version 8. Add participant/thread stores.");
self.upgradeSchema7(db, event.target.transaction);
break;
case 8:
if (DEBUG) debug("Upgrade to version 9. Add transactionId index for incoming MMS.");
self.upgradeSchema8(event.target.transaction);
break;
default:
event.target.transaction.abort();
callback("Old database version: " + event.oldVersion, null);
@ -559,6 +564,39 @@ MobileMessageDatabaseService.prototype = {
};
},
/**
* Add transactionId index for MMS.
*/
upgradeSchema8: function upgradeSchema8(transaction) {
let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
// Delete "transactionId" index.
if (messageStore.indexNames.contains("transactionId")) {
messageStore.deleteIndex("transactionId");
}
// Create new "transactionId" indexes.
messageStore.createIndex("transactionId", "transactionIdIndex", { unique: true });
// Populate new "transactionIdIndex" attributes.
messageStore.openCursor().onsuccess = function(event) {
let cursor = event.target.result;
if (!cursor) {
return;
}
let messageRecord = cursor.value;
if ("mms" == messageRecord.type &&
(DELIVERY_NOT_DOWNLOADED == messageRecord.delivery ||
DELIVERY_RECEIVED == messageRecord.delivery)) {
messageRecord.transactionIdIndex =
messageRecord.headers["x-mms-transaction-id"];
cursor.update(messageRecord);
}
cursor.continue();
};
},
createDomMessageFromRecord: function createDomMessageFromRecord(aMessageRecord) {
if (DEBUG) {
debug("createDomMessageFromRecord: " + JSON.stringify(aMessageRecord));
@ -908,6 +946,7 @@ MobileMessageDatabaseService.prototype = {
if ((aMessage.type != "sms" && aMessage.type != "mms") ||
(aMessage.type == "sms" && aMessage.messageClass == undefined) ||
(aMessage.type == "mms" && (aMessage.delivery == undefined ||
aMessage.transactionId == undefined ||
!Array.isArray(aMessage.deliveryStatus) ||
!Array.isArray(aMessage.receivers))) ||
aMessage.sender == undefined ||
@ -950,6 +989,10 @@ MobileMessageDatabaseService.prototype = {
aMessage.readIndex = [FILTER_READ_UNREAD, timestamp];
aMessage.read = FILTER_READ_UNREAD;
if (aMessage.type == "mms") {
aMessage.transactionIdIndex = aMessage.transactionId;
}
if (aMessage.type == "sms") {
aMessage.delivery = DELIVERY_RECEIVED;
aMessage.deliveryStatus = DELIVERY_STATUS_SUCCESS;
@ -1120,6 +1163,37 @@ MobileMessageDatabaseService.prototype = {
});
},
getMessageRecordByTransactionId: function getMessageRecordByTransactionId(aTransactionId, aCallback) {
if (DEBUG) debug("Retrieving message with transaction ID " + aTransactionId);
this.newTxn(READ_ONLY, function (error, txn, messageStore) {
if (error) {
if (DEBUG) debug(error);
aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null);
return;
}
let request = messageStore.index("transactionId").get(aTransactionId);
txn.oncomplete = function oncomplete(event) {
if (DEBUG) debug("Transaction " + txn + " completed.");
let messageRecord = request.result;
if (!messageRecord) {
if (DEBUG) debug("Transaction ID " + aTransactionId + " not found");
aCallback.notify(Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR, null);
return;
}
aCallback.notify(Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR, messageRecord);
};
txn.onerror = function onerror(event) {
if (DEBUG) {
if (event.target)
debug("Caught error on transaction", event.target.errorCode);
}
aCallback.notify(Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null);
};
});
},
getMessageRecordById: function getMessageRecordById(aMessageId, aCallback) {
if (DEBUG) debug("Retrieving message with ID " + aMessageId);
this.newTxn(READ_ONLY, function (error, txn, messageStore) {