Merge b2g-inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Mon, 25 Nov 2013 14:05:01 -0500
changeset 157420 5a76a37f6d3cac00b6897bd6f7dc9a4cc16abd08
parent 157387 bc69ab4c3a228877a1f842bc41f56e36262c01d5 (current diff)
parent 157419 8a9df675d954c5aec7a43177a2ff9a09c45590df (diff)
child 157421 53d55d2d0a25282cca238e6df17b68b3513f9430
push id36704
push userryanvm@gmail.com
push dateMon, 25 Nov 2013 19:23:54 +0000
treeherdermozilla-inbound@4620af706632 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to m-c.
--- a/CLOBBER
+++ b/CLOBBER
@@ -13,9 +13,9 @@
 #          |               |
 #          O <-- Clobber   O  <-- Clobber
 #
 # Note: The description below will be part of the error message shown to users.
 #
 # Modifying this file will now automatically clobber the buildbot machines \o/
 #
 
-Bug 941844 - Because Android resource builds are flaky.
+Bug 921918 - need clobber for Windows.
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "2a055e0d4214f04646c15c09a6a03bf96f347689", 
+    "revision": "aac07cb753d6c364c3cbd39ad8116d455d5adf01", 
     "repo_path": "/integration/gaia-central"
 }
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -766,16 +766,18 @@ GK_ATOM(onpageshow, "onpageshow")
 GK_ATOM(onpaint, "onpaint")
 GK_ATOM(onpairedstatuschanged, "onpairedstatuschanged")
 GK_ATOM(onpaste, "onpaste")
 GK_ATOM(onpopuphidden, "onpopuphidden")
 GK_ATOM(onpopuphiding, "onpopuphiding")
 GK_ATOM(onpopupshowing, "onpopupshowing")
 GK_ATOM(onpopupshown, "onpopupshown")
 GK_ATOM(onradiostatechange, "onradiostatechange")
+GK_ATOM(onreaderror, "onreaderror")
+GK_ATOM(onreadsuccess, "onreadsuccess")
 GK_ATOM(onreadystatechange, "onreadystatechange")
 GK_ATOM(onreceived, "onreceived")
 GK_ATOM(onremoteheld, "onremoteheld")
 GK_ATOM(onremoteresumed, "onremoteresumed")
 GK_ATOM(onretrieving, "onretrieving")
 GK_ATOM(onRequest, "onRequest")
 GK_ATOM(onrequestmediaplaystatus, "onrequestmediaplaystatus")
 GK_ATOM(onreset, "onreset")
--- a/content/media/MediaRecorder.cpp
+++ b/content/media/MediaRecorder.cpp
@@ -74,21 +74,23 @@ class MediaRecorder::Session: public nsI
       : mSession(aSession)
     { }
 
     NS_IMETHODIMP Run()
     {
       MOZ_ASSERT(NS_IsMainThread());
 
       MediaRecorder *recorder = mSession->mRecorder;
+      if (mSession->IsEncoderError()) {
+        recorder->NotifyError(NS_ERROR_UNEXPECTED);
+      }
       nsresult rv = recorder->CreateAndDispatchBlobEvent(mSession);
       if (NS_FAILED(rv)) {
         recorder->NotifyError(rv);
       }
-
       return NS_OK;
     }
 
   private:
     Session *mSession;
   };
 
   // Record thread task.
@@ -220,16 +222,23 @@ public:
   already_AddRefed<nsIDOMBlob> GetEncodedData()
   {
     nsString mimeType;
     mRecorder->GetMimeType(mimeType);
 
     return mEncodedBufferCache->ExtractBlob(mimeType);
   }
 
+  bool IsEncoderError()
+  {
+    if (mEncoder && mEncoder->HasError()) {
+      return true;
+    }
+    return false;
+  }
 private:
 
   // Pull encoded meida data from MediaEncoder and put into EncodedBufferCache.
   // Destroy this session object in the end of this function.
   void Extract()
   {
     MOZ_ASSERT(NS_GetCurrentThread() == mReadThread);
 
--- a/content/media/encoder/MediaEncoder.cpp
+++ b/content/media/encoder/MediaEncoder.cpp
@@ -143,65 +143,71 @@ MediaEncoder::CreateEncoder(const nsAStr
  *     Get encoded track data from audio/video encoder
  *     If a packet of track data is generated
  *       Insert encoded track data into the container stream of writer
  *       If the final container data is copied to aOutput
  *         Return the copy of final container data
  *       If this is the last packet of input stream
  *         Set mState to ENCODE_DONE
  *
- *   If mState is ENCODE_DONE
+ *   If mState is ENCODE_DONE or ENCODE_ERROR
  *     Stop the loop
  */
 void
 MediaEncoder::GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
                              nsAString& aMIMEType)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   aMIMEType = mMIMEType;
 
   bool reloop = true;
   while (reloop) {
     switch (mState) {
     case ENCODE_METADDATA: {
       nsRefPtr<TrackMetadataBase> meta = mAudioEncoder->GetMetadata();
-      MOZ_ASSERT(meta);
+      if (meta == nullptr) {
+        LOG("ERROR! AudioEncoder get null Metadata!");
+        mState = ENCODE_ERROR;
+        break;
+      }
       nsresult rv = mWriter->SetMetadata(meta);
       if (NS_FAILED(rv)) {
-       mState = ENCODE_DONE;
+       LOG("ERROR! writer can't accept audio metadata!");
+       mState = ENCODE_ERROR;
        break;
       }
 
       rv = mWriter->GetContainerData(aOutputBufs,
                                      ContainerWriter::GET_HEADER);
       if (NS_FAILED(rv)) {
-       mState = ENCODE_DONE;
+       LOG("ERROR! writer fail to generate header!");
+       mState = ENCODE_ERROR;
        break;
       }
 
       mState = ENCODE_TRACK;
       break;
     }
 
     case ENCODE_TRACK: {
       EncodedFrameContainer encodedData;
       nsresult rv = mAudioEncoder->GetEncodedTrack(encodedData);
       if (NS_FAILED(rv)) {
         // Encoding might be canceled.
         LOG("ERROR! Fail to get encoded data from encoder.");
-        mState = ENCODE_DONE;
+        mState = ENCODE_ERROR;
         break;
       }
       rv = mWriter->WriteEncodedTrack(encodedData,
                                       mAudioEncoder->IsEncodingComplete() ?
                                       ContainerWriter::END_OF_STREAM : 0);
       if (NS_FAILED(rv)) {
         LOG("ERROR! Fail to write encoded track to the media container.");
-        mState = ENCODE_DONE;
+        mState = ENCODE_ERROR;
         break;
       }
 
       rv = mWriter->GetContainerData(aOutputBufs,
                                      mAudioEncoder->IsEncodingComplete() ?
                                      ContainerWriter::FLUSH_NEEDED : 0);
       if (NS_SUCCEEDED(rv)) {
         // Successfully get the copy of final container data from writer.
@@ -212,16 +218,20 @@ MediaEncoder::GetEncodedData(nsTArray<ns
       break;
     }
 
     case ENCODE_DONE:
       LOG("MediaEncoder has been shutdown.");
       mShutdown = true;
       reloop = false;
       break;
-
+    case ENCODE_ERROR:
+      LOG("ERROR! MediaEncoder got error!");
+      mShutdown = true;
+      reloop = false;
+      break;
     default:
       MOZ_CRASH("Invalid encode state");
     }
   }
 }
 
 }
--- a/content/media/encoder/MediaEncoder.h
+++ b/content/media/encoder/MediaEncoder.h
@@ -49,16 +49,17 @@ namespace mozilla {
  */
 class MediaEncoder : public MediaStreamListener
 {
 public :
   enum {
     ENCODE_METADDATA,
     ENCODE_TRACK,
     ENCODE_DONE,
+    ENCODE_ERROR,
   };
 
   MediaEncoder(ContainerWriter* aWriter,
                AudioTrackEncoder* aAudioEncoder,
                VideoTrackEncoder* aVideoEncoder,
                const nsAString& aMIMEType)
     : mWriter(aWriter)
     , mAudioEncoder(aAudioEncoder)
@@ -116,16 +117,21 @@ public :
    */
   void Cancel()
   {
     if (mAudioEncoder) {
       mAudioEncoder->NotifyCancel();
     }
   }
 
+  bool HasError()
+  {
+    return mState == ENCODE_ERROR;
+  }
+
 private:
   nsAutoPtr<ContainerWriter> mWriter;
   nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
   nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
   nsString mMIMEType;
   int mState;
   bool mShutdown;
 };
--- a/content/media/encoder/OpusTrackEncoder.cpp
+++ b/content/media/encoder/OpusTrackEncoder.cpp
@@ -131,28 +131,27 @@ OpusTrackEncoder::~OpusTrackEncoder()
     speex_resampler_destroy(mResampler);
   }
 
 }
 
 nsresult
 OpusTrackEncoder::Init(int aChannels, int aSamplingRate)
 {
-  // The track must have 1 or 2 channels.
-  if (aChannels <= 0 || aChannels > MAX_CHANNELS) {
-    LOG("[Opus] Fail to create the AudioTrackEncoder! The input has"
-        " %d channel(s), but expects no more than %d.", aChannels, MAX_CHANNELS);
-    return NS_ERROR_INVALID_ARG;
-  }
-
   // This monitor is used to wake up other methods that are waiting for encoder
   // to be completely initialized.
   ReentrantMonitorAutoEnter mon(mReentrantMonitor);
-  mChannels = aChannels;
+  // This version of encoder API only support 1 or 2 channels,
+  // So set the mChannels less or equal 2 and
+  // let InterleaveTrackData downmix pcm data.
+  mChannels = aChannels > 2 ? 2 : aChannels;
 
+  if (aChannels <= 0) {
+    return NS_ERROR_FAILURE;
+  }
   // The granule position is required to be incremented at a rate of 48KHz, and
   // it is simply calculated as |granulepos = samples * (48000/source_rate)|,
   // that is, the source sampling rate must divide 48000 evenly.
   // If this constraint is not satisfied, we resample the input to 48kHz.
   if (!((aSamplingRate >= 8000) && (kOpusSamplingRate / aSamplingRate) *
          aSamplingRate == kOpusSamplingRate)) {
     int error;
     mResampler = speex_resampler_init(mChannels,
@@ -321,20 +320,16 @@ OpusTrackEncoder::GetEncodedTrack(Encode
   // The value of frameCopied should equal to (or smaller than, if eos)
   // GetPacketDuration().
   mSourceSegment->RemoveLeading(frameCopied);
 
   // Has reached the end of input stream and all queued data has pulled for
   // encoding.
   if (mSourceSegment->GetDuration() == 0 && mEndOfStream) {
     mDoneEncoding = true;
-    if (mResampler) {
-      speex_resampler_destroy(mResampler);
-      mResampler = nullptr;
-    }
     LOG("[Opus] Done encoding.");
   }
 
   // Append null data to pcm buffer if the leftover data is not enough for
   // opus encoder.
   if (frameCopied < GetPacketDuration() && mEndOfStream) {
     memset(pcm.Elements() + frameCopied * mChannels, 0,
            (GetPacketDuration()-frameCopied)*mChannels*sizeof(AudioDataValue));
@@ -353,15 +348,21 @@ OpusTrackEncoder::GetEncodedTrack(Encode
   result = opus_encode_float(mEncoder, pcmBuf, GetPacketDuration(),
                              frameData.Elements(), MAX_DATA_BYTES);
 #endif
   frameData.SetLength(result >= 0 ? result : 0);
 
   if (result < 0) {
     LOG("[Opus] Fail to encode data! Result: %s.", opus_strerror(result));
   }
+  if (mDoneEncoding) {
+    if (mResampler) {
+      speex_resampler_destroy(mResampler);
+      mResampler = nullptr;
+    }
+  }
 
   audiodata->SetFrameData(&frameData);
   aData.AppendEncodedFrame(audiodata);
   return result >= 0 ? NS_OK : NS_ERROR_FAILURE;
 }
 
 }
--- a/content/media/encoder/TrackEncoder.h
+++ b/content/media/encoder/TrackEncoder.h
@@ -136,18 +136,20 @@ protected:
    * to up-mix or down-mix the channel data if the channels number of this chunk
    * is different from mChannels. The channel data from aChunk might be modified
    * by up-mixing.
    */
   void InterleaveTrackData(AudioChunk& aChunk, int32_t aDuration,
                            uint32_t aOutputChannels, AudioDataValue* aOutput);
 
   /**
-   * The number of channels in the first valid audio chunk, and is being used
-   * to initialize the audio encoder.
+   * The number of channels are used for processing PCM data in the audio encoder.
+   * This value comes from the first valid audio chunk. If encoder can't support
+   * the channels in the chunk, downmix PCM stream can be performed.
+   * This value also be used to initialize the audio encoder.
    */
   int mChannels;
   int mSamplingRate;
   bool mInitialized;
   bool mDoneEncoding;
 
   /**
    * A ReentrantMonitor to protect the pushing and pulling of mRawSegment.
--- a/content/media/ogg/OggWriter.cpp
+++ b/content/media/ogg/OggWriter.cpp
@@ -164,16 +164,17 @@ OggWriter::GetContainerData(nsTArray<nsT
   }
 
   return (rc > 0) ? NS_OK : NS_ERROR_FAILURE;
 }
 
 nsresult
 OggWriter::SetMetadata(TrackMetadataBase* aMetadata)
 {
+  MOZ_ASSERT(aMetadata);
   if (aMetadata->GetKind() != TrackMetadataBase::METADATA_OPUS) {
     LOG("wrong meta data type!");
     return NS_ERROR_FAILURE;
   }
   // Validate each field of METADATA
   mMetadata = static_cast<OpusMetadata*>(aMetadata);
   if (mMetadata->mIdHeader.Length() == 0) {
     LOG("miss mIdHeader!");
--- a/content/media/test/mochitest.ini
+++ b/content/media/test/mochitest.ini
@@ -232,16 +232,17 @@ support-files =
 [test_standalone.html]
 [test_volume.html]
 [test_video_to_canvas.html]
 [test_audiowrite.html]
 [test_mediarecorder_creation.html]
 [test_mediarecorder_avoid_recursion.html]
 [test_mediarecorder_record_timeslice.html]
 [test_mediarecorder_record_audiocontext.html]
+[test_mediarecorder_record_4ch_audiocontext.html]
 [test_mediarecorder_record_stopms.html]
 [test_mediarecorder_record_nosrc.html]
 [test_mozHasAudio.html]
 [test_source_media.html]
 [test_autoplay_contentEditable.html]
 [test_decoder_disable.html]
 [test_mediarecorder_record_no_timeslice.html]
 [test_mediarecorder_reload_crash.html]
new file mode 100644
--- /dev/null
+++ b/content/media/test/test_mediarecorder_record_4ch_audiocontext.html
@@ -0,0 +1,74 @@
+<!DOCTYPE HTML>
+<html>
+<head>
+  <title>Test MediaRecorder Record AudioContext with four channels</title>
+  <script type="text/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>
+  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css" />
+  <script type="text/javascript" src="manifest.js"></script>
+</head>
+<body>
+
+<script class="testbody" type="text/javascript">
+
+function startTest() {
+  var context = new AudioContext();
+  var buffer = context.createBuffer(4, 80920, context.sampleRate);
+  for (var i = 0; i < 80920; ++i) {
+    for(var j = 0; j < 4; ++j) {
+      buffer.getChannelData(j)[i] = Math.sin(1000 * 2 * Math.PI * i / context.sampleRate);
+    }
+  }
+
+  var source = context.createBufferSource();
+  source.buffer = buffer;
+  var dest = context.createMediaStreamDestination();
+  var stopTriggered = false;
+  var onstopTriggered = false;
+  dest.channelCount = 4;
+  var expectedMimeType = 'audio/ogg';
+  source.channelCountMode = 'explicit';
+  source.connect(dest);
+  var elem = document.createElement('audio');
+  elem.mozSrcObject = dest.stream;
+  mMediaStream = dest.stream;
+  source.start(0);
+  elem.play();
+  mMediaRecorder = new MediaRecorder(dest.stream);
+  mMediaRecorder.onwarning = function() {
+    ok(false, 'onwarning unexpectedly fired');
+  };
+
+  mMediaRecorder.onerror = function() {
+    ok(false, 'onerror unexpectedly fired');
+  };
+
+  mMediaRecorder.onstop = function() {
+    ok(true, 'onstop fired successfully');
+    is(mMediaRecorder.state, 'inactive', 'check recording status is inactive');
+    onstopTriggered = true;
+    SimpleTest.finish();
+  };
+  mMediaRecorder.ondataavailable = function (e) {
+    ok(e.data.size > 0, 'check blob has data');
+    is(mMediaRecorder.mimeType, expectedMimeType, 'blob should has mimetype, return ' + mMediaRecorder.mimeType);
+    if (!stopTriggered) {
+      mMediaRecorder.stop();
+      stopTriggered = true;
+    } else if (onstopTriggered) {
+      ok(false, 'ondataavailable should come before onstop event');
+    }
+  };
+  try {
+    mMediaRecorder.start(1000);
+    is('recording', mMediaRecorder.state, "check record state recording");
+  } catch (e) {
+    ok(false, 'Can t record audio context');
+  }
+}
+
+startTest();
+SimpleTest.waitForExplicitFinish();
+</script>
+</pre>
+</body>
+</html>
--- a/dom/messages/SystemMessagePermissionsChecker.jsm
+++ b/dom/messages/SystemMessagePermissionsChecker.jsm
@@ -69,16 +69,19 @@ this.SystemMessagePermissionsTable = {
   	"push": []
   },
   "push-register": {
   	"push": []
   },
   "sms-delivery-success": {
     "sms": []
   },
+  "sms-read-success": {
+    "sms": []
+  },
   "sms-received": {
     "sms": []
   },
   "sms-sent": {
     "sms": []
   },
   "telephony-new-call": {
     "telephony": []
--- a/dom/mobilemessage/interfaces/nsIDOMMobileMessageManager.idl
+++ b/dom/mobilemessage/interfaces/nsIDOMMobileMessageManager.idl
@@ -6,17 +6,17 @@
 
 interface nsIDOMEventListener;
 interface nsIDOMMozSmsFilter;
 interface nsIDOMMozSmsSegmentInfo;
 interface nsIDOMDOMCursor;
 interface nsIDOMDOMRequest;
 interface nsIDOMBlob;
 
-[scriptable, builtinclass, uuid(cfcc7067-083f-4e09-91aa-75067a721b70)]
+[scriptable, builtinclass, uuid(a99c3538-a8d6-492f-9ece-f6e92f9c00c5)]
 interface nsIDOMMozMobileMessageManager : nsIDOMEventTarget
 {
   nsIDOMDOMRequest getSegmentInfoForText(in DOMString text);
 
 
   /**
    * Function to send SMS.
    *
@@ -70,9 +70,11 @@ interface nsIDOMMozMobileMessageManager 
 
   [implicit_jscontext] attribute jsval onreceived;
   [implicit_jscontext] attribute jsval onretrieving;
   [implicit_jscontext] attribute jsval onsending;
   [implicit_jscontext] attribute jsval onsent;
   [implicit_jscontext] attribute jsval onfailed;
   [implicit_jscontext] attribute jsval ondeliverysuccess;
   [implicit_jscontext] attribute jsval ondeliveryerror;
+  [implicit_jscontext] attribute jsval onreadsuccess;
+  [implicit_jscontext] attribute jsval onreaderror;
 };
--- a/dom/mobilemessage/interfaces/nsIDOMMozMmsMessage.idl
+++ b/dom/mobilemessage/interfaces/nsIDOMMozMmsMessage.idl
@@ -16,16 +16,19 @@ dictionary MmsAttachment
 };
 
 dictionary MmsDeliveryInfo
 {
   DOMString? receiver;
   DOMString? deliveryStatus;
   jsval      deliveryTimestamp; // Date object; null if not available (e.g.,
                                 // |delivery| = "received" or not yet delivered).
+  DOMString? readStatus;
+  jsval      readTimestamp; // Date object. null if not available (e.g.,
+                            // |delivery| = "received" or not yet read).
 };
 
 [scriptable, builtinclass, uuid(82ca2465-f967-4107-a4da-65b7a15d5dba)]
 interface nsIDOMMozMmsMessage : nsISupports
 {
   /**
    * |type| is always "mms".
    */
--- a/dom/mobilemessage/interfaces/nsIRilMobileMessageDatabaseService.idl
+++ b/dom/mobilemessage/interfaces/nsIRilMobileMessageDatabaseService.idl
@@ -20,40 +20,37 @@ interface nsIRilMobileMessageDatabaseRec
 {
   /**
    * |aMessageRecord| Object: the mobile-message database record
    * |aDomMessage|: the nsIDOMMoz{Mms,Sms}Message. Noted, this value might be null.
    */
   void notify(in nsresult aRv, in jsval aMessageRecord, in nsISupports aDomMessage);
 };
 
-[scriptable, uuid(d5374151-7451-4590-a70e-40c49c1369ce)]
+[scriptable, uuid(a92eba51-e619-4f70-98c5-175a33590582)]
 interface nsIRilMobileMessageDatabaseService : nsIMobileMessageDatabaseService
 {
   /**
    * |aMessage| Object: should contain the following properties for internal use:
    *   - |type| DOMString: "sms" or "mms"
-   *   - |sender| DOMString: the phone number of sender
    *   - |timestamp| Number: the timestamp of received message
-   *   - |iccId| DOMString: the ICC ID of the SIM for receiving message
+   *   - |iccId| DOMString: [optional] the ICC ID of the SIM for receiving
+   *                        message if available.
    *
    *   - If |type| == "sms", we also need:
    *     - |messageClass| DOMString: the message class of received message
    *     - |receiver| DOMString: the phone number of receiver
    *     - |pid| Number: the TP-PID field of the SMS TPDU, default 0.
+   *     - |sender| DOMString: the phone number of sender
    *
    *   - If |type| == "mms", we also need:
    *     - |delivery| DOMString: the delivery state of received message
-   *     - |deliveryStatus| DOMString Array: the delivery status of received message
+   *     - |deliveryStatus| DOMString: the delivery status of received message
    *     - |receivers| DOMString Array: the phone numbers of receivers
    *     - |phoneNumber| DOMString: [optional] my own phone number.
-   *     - |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.
    */
   void saveReceivedMessage(in jsval aMessage,
                 [optional] in nsIRilMobileMessageDatabaseCallback aCallback);
 
   /**
    * |aMessage| Object: should contain the following properties for internal use:
    *   - |type| DOMString: "sms" or "mms"
    *   - |sender| DOMString: the phone number of sender
@@ -83,25 +80,34 @@ interface nsIRilMobileMessageDatabaseSer
                                      in DOMString aDelivery,
                                      in DOMString aDeliveryStatus,
                                      in DOMString aEnvelopeId,
                           [optional] in nsIRilMobileMessageDatabaseCallback aCallback);
 
   /**
    * |aEnvelopeId| DOMString: the "message-id" specified in the MMS PDU headers.
    * |aReceiver| DOMString: the phone number of receiver (for MMS; can be null).
-   * |aDelivery| DOMString: the new delivery value to update (can be null).
-   * |aDeliveryStatus| DOMString: the new delivery status to update (can be null).
+   * |aDeliveryStatus| DOMString: the new delivery status to be updated (can be null).
    * |aCallback| nsIRilMobileMessageDatabaseCallback: an optional callback.
    */
-  void setMessageDeliveryByEnvelopeId(in DOMString aEnvelopeId,
-                                      in DOMString aReceiver,
-                                      in DOMString aDelivery,
-                                      in DOMString aDeliveryStatus,
-                           [optional] in nsIRilMobileMessageDatabaseCallback aCallback);
+  void setMessageDeliveryStatusByEnvelopeId(in DOMString aEnvelopeId,
+                                            in DOMString aReceiver,
+                                            in DOMString aDeliveryStatus,
+                                 [optional] in nsIRilMobileMessageDatabaseCallback aCallback);
+
+  /**
+   * |aEnvelopeId| DOMString: the "message-id" specified in the MMS PDU headers.
+   * |aReceiver| DOMString: the phone number of receiver (for MMS; can be null).
+   * |aReadStatus| DOMString: the new read status to be updated.
+   * |aCallback| nsIRilMobileMessageDatabaseCallback: an optional callback.
+   */
+  void setMessageReadStatusByEnvelopeId(in DOMString aEnvelopeId,
+                                        in DOMString aReceiver,
+                                        in DOMString aReadStatus,
+                             [optional] in nsIRilMobileMessageDatabaseCallback aCallback);
 
   /**
    * |aMessageId| Number: the message's DB record ID.
    * |aCallback| nsIRilMobileMessageDatabaseRecordCallback: a callback which
    *   takes result flag, message record and domMessage as parameters.
    */
   void getMessageRecordById(in long aMessageId,
                             in nsIRilMobileMessageDatabaseRecordCallback aCallback);
--- a/dom/mobilemessage/src/Constants.cpp
+++ b/dom/mobilemessage/src/Constants.cpp
@@ -10,12 +10,14 @@ namespace mobilemessage {
 const char* kSmsReceivedObserverTopic        = "sms-received";
 const char* kSmsRetrievingObserverTopic      = "sms-retrieving";
 const char* kSmsSendingObserverTopic         = "sms-sending";
 const char* kSmsSentObserverTopic            = "sms-sent";
 const char* kSmsFailedObserverTopic          = "sms-failed";
 const char* kSmsDeliverySuccessObserverTopic = "sms-delivery-success";
 const char* kSmsDeliveryErrorObserverTopic   = "sms-delivery-error";
 const char* kSilentSmsReceivedObserverTopic  = "silent-sms-received";
+const char* kSmsReadSuccessObserverTopic     = "sms-read-success";
+const char* kSmsReadErrorObserverTopic       = "sms-read-error";
 
 } // namespace mobilemessage
 } // namespace dom
 } // namespace mozilla
--- a/dom/mobilemessage/src/Constants.h
+++ b/dom/mobilemessage/src/Constants.h
@@ -14,30 +14,37 @@ namespace mobilemessage {
 extern const char* kSmsReceivedObserverTopic;
 extern const char* kSmsRetrievingObserverTopic;
 extern const char* kSmsSendingObserverTopic;
 extern const char* kSmsSentObserverTopic;
 extern const char* kSmsFailedObserverTopic;
 extern const char* kSmsDeliverySuccessObserverTopic;
 extern const char* kSmsDeliveryErrorObserverTopic;
 extern const char* kSilentSmsReceivedObserverTopic;
+extern const char* kSmsReadSuccessObserverTopic;
+extern const char* kSmsReadErrorObserverTopic;
 
 #define DELIVERY_RECEIVED       NS_LITERAL_STRING("received")
 #define DELIVERY_SENDING        NS_LITERAL_STRING("sending")
 #define DELIVERY_SENT           NS_LITERAL_STRING("sent")
 #define DELIVERY_ERROR          NS_LITERAL_STRING("error")
 #define DELIVERY_NOT_DOWNLOADED NS_LITERAL_STRING("not-downloaded")
 
 #define DELIVERY_STATUS_NOT_APPLICABLE NS_LITERAL_STRING("not-applicable")
 #define DELIVERY_STATUS_SUCCESS        NS_LITERAL_STRING("success")
 #define DELIVERY_STATUS_PENDING        NS_LITERAL_STRING("pending")
 #define DELIVERY_STATUS_ERROR          NS_LITERAL_STRING("error")
 #define DELIVERY_STATUS_REJECTED       NS_LITERAL_STRING("rejected")
 #define DELIVERY_STATUS_MANUAL         NS_LITERAL_STRING("manual")
 
+#define READ_STATUS_NOT_APPLICABLE NS_LITERAL_STRING("not-applicable")
+#define READ_STATUS_SUCCESS        NS_LITERAL_STRING("success")
+#define READ_STATUS_PENDING        NS_LITERAL_STRING("pending")
+#define READ_STATUS_ERROR          NS_LITERAL_STRING("error")
+
 #define MESSAGE_CLASS_NORMAL  NS_LITERAL_STRING("normal")
 #define MESSAGE_CLASS_CLASS_0 NS_LITERAL_STRING("class-0")
 #define MESSAGE_CLASS_CLASS_1 NS_LITERAL_STRING("class-1")
 #define MESSAGE_CLASS_CLASS_2 NS_LITERAL_STRING("class-2")
 #define MESSAGE_CLASS_CLASS_3 NS_LITERAL_STRING("class-3")
 
 #define MESSAGE_TYPE_SMS NS_LITERAL_STRING("sms")
 #define MESSAGE_TYPE_MMS NS_LITERAL_STRING("mms")
--- a/dom/mobilemessage/src/MmsMessage.cpp
+++ b/dom/mobilemessage/src/MmsMessage.cpp
@@ -140,16 +140,50 @@ MmsMessage::MmsMessage(const mobilemessa
         dateObj(cx, JS_NewDateObjectMsec(cx, infoData.deliveryTimestamp()));
       if (!dateObj) {
         NS_WARNING("MmsMessage: Unable to create Date for deliveryTimestamp.");
       } else {
         info.deliveryTimestamp = OBJECT_TO_JSVAL(dateObj);
       }
     }
 
+    // Prepare |info.readStatus|.
+    nsString statusReadString;
+    switch(infoData.readStatus()) {
+      case eReadStatus_NotApplicable:
+        statusReadString = READ_STATUS_NOT_APPLICABLE;
+        break;
+      case eReadStatus_Success:
+        statusReadString = READ_STATUS_SUCCESS;
+        break;
+      case eReadStatus_Pending:
+        statusReadString = READ_STATUS_PENDING;
+        break;
+      case eReadStatus_Error:
+        statusReadString = READ_STATUS_ERROR;
+        break;
+      case eReadStatus_EndGuard:
+      default:
+        MOZ_CRASH("We shouldn't get any other read status!");
+    }
+    info.readStatus = statusReadString;
+
+    // Prepare |info.readTimestamp|.
+    info.readTimestamp = JSVAL_NULL;
+    if (infoData.readTimestamp() != 0) {
+      AutoJSContext cx;
+      JS::Rooted<JSObject*>
+        dateObj(cx, JS_NewDateObjectMsec(cx, infoData.readTimestamp()));
+      if (!dateObj) {
+        NS_WARNING("MmsMessage: Unable to create Data for readTimestamp.");
+      } else {
+        info.readTimestamp = OBJECT_TO_JSVAL(dateObj);
+      }
+    }
+
     mDeliveryInfo.AppendElement(info);
   }
 }
 
 /**
  * A helper function to convert the JS value to an integer value for time.
  *
  * @params aCx
@@ -371,16 +405,39 @@ MmsMessage::GetData(ContentParent* aPare
     // Prepare |infoData.deliveryTimestamp|.
     if (info.deliveryTimestamp == JSVAL_NULL) {
       infoData.deliveryTimestamp() = 0;
     } else {
       AutoJSContext cx;
       convertTimeToInt(cx, info.deliveryTimestamp, infoData.deliveryTimestamp());
     }
 
+    // Prepare |infoData.readStatus|.
+    ReadStatus readStatus;
+    if (info.readStatus.Equals(READ_STATUS_NOT_APPLICABLE)) {
+      readStatus = eReadStatus_NotApplicable;
+    } else if (info.readStatus.Equals(READ_STATUS_SUCCESS)) {
+      readStatus = eReadStatus_Success;
+    } else if (info.readStatus.Equals(READ_STATUS_PENDING)) {
+      readStatus = eReadStatus_Pending;
+    } else if (info.readStatus.Equals(READ_STATUS_ERROR)) {
+      readStatus = eReadStatus_Error;
+    } else {
+      return false;
+    }
+    infoData.readStatus() = readStatus;
+
+    // Prepare |infoData.readTimestamp|.
+    if (info.readTimestamp == JSVAL_NULL) {
+      infoData.readTimestamp() = 0;
+    } else {
+      AutoJSContext cx;
+      convertTimeToInt(cx, info.readTimestamp, infoData.readTimestamp());
+    }
+
     aData.deliveryInfo().AppendElement(infoData);
   }
 
   aData.attachments().SetCapacity(mAttachments.Length());
   for (uint32_t i = 0; i < mAttachments.Length(); i++) {
     MmsAttachmentData mma;
     const MmsAttachment &element = mAttachments[i];
     mma.id().Assign(element.id);
@@ -516,16 +573,34 @@ MmsMessage::GetDeliveryInfo(JSContext* a
 
     // Get |info.deliveryTimestamp|.
     if (!JS_DefineProperty(aCx, infoJsObj,
                            "deliveryTimestamp", info.deliveryTimestamp,
                            nullptr, nullptr, JSPROP_ENUMERATE)) {
       return NS_ERROR_FAILURE;
     }
 
+    // Get |info.readStatus|.
+    tmpJsStr = JS_NewUCStringCopyN(aCx,
+                                   info.readStatus.get(),
+                                   info.readStatus.Length());
+    NS_ENSURE_TRUE(tmpJsStr, NS_ERROR_OUT_OF_MEMORY);
+
+    tmpJsVal.setString(tmpJsStr);
+    if (!JS_DefineProperty(aCx, infoJsObj, "readStatus", tmpJsVal,
+                           NULL, NULL, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
+    }
+
+    // Get |info.readTimestamp|.
+    if (!JS_DefineProperty(aCx, infoJsObj, "readTimestamp", info.readTimestamp,
+                           NULL, NULL, JSPROP_ENUMERATE)) {
+      return NS_ERROR_FAILURE;
+    }
+
     tmpJsVal = OBJECT_TO_JSVAL(infoJsObj);
     if (!JS_SetElement(aCx, deliveryInfo, i, &tmpJsVal)) {
       return NS_ERROR_FAILURE;
     }
   }
 
   aDeliveryInfo->setObject(*deliveryInfo);
   return NS_OK;
--- a/dom/mobilemessage/src/MobileMessageManager.cpp
+++ b/dom/mobilemessage/src/MobileMessageManager.cpp
@@ -33,16 +33,18 @@
 
 #define RECEIVED_EVENT_NAME         NS_LITERAL_STRING("received")
 #define RETRIEVING_EVENT_NAME       NS_LITERAL_STRING("retrieving")
 #define SENDING_EVENT_NAME          NS_LITERAL_STRING("sending")
 #define SENT_EVENT_NAME             NS_LITERAL_STRING("sent")
 #define FAILED_EVENT_NAME           NS_LITERAL_STRING("failed")
 #define DELIVERY_SUCCESS_EVENT_NAME NS_LITERAL_STRING("deliverysuccess")
 #define DELIVERY_ERROR_EVENT_NAME   NS_LITERAL_STRING("deliveryerror")
+#define READ_SUCCESS_EVENT_NAME     NS_LITERAL_STRING("readsuccess")
+#define READ_ERROR_EVENT_NAME       NS_LITERAL_STRING("readerror")
 
 using namespace mozilla::dom::mobilemessage;
 
 DOMCI_DATA(MozMobileMessageManager, mozilla::dom::MobileMessageManager)
 
 namespace mozilla {
 namespace dom {
 
@@ -57,16 +59,18 @@ NS_IMPL_RELEASE_INHERITED(MobileMessageM
 
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, received)
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, retrieving)
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, sending)
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, sent)
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, failed)
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, deliverysuccess)
 NS_IMPL_EVENT_HANDLER(MobileMessageManager, deliveryerror)
+NS_IMPL_EVENT_HANDLER(MobileMessageManager, readsuccess)
+NS_IMPL_EVENT_HANDLER(MobileMessageManager, readerror)
 
 void
 MobileMessageManager::Init(nsPIDOMWindow *aWindow)
 {
   BindToOwner(aWindow);
 
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   // GetObserverService() can return null is some situations like shutdown.
@@ -76,16 +80,18 @@ MobileMessageManager::Init(nsPIDOMWindow
 
   obs->AddObserver(this, kSmsReceivedObserverTopic, false);
   obs->AddObserver(this, kSmsRetrievingObserverTopic, false);
   obs->AddObserver(this, kSmsSendingObserverTopic, false);
   obs->AddObserver(this, kSmsSentObserverTopic, false);
   obs->AddObserver(this, kSmsFailedObserverTopic, false);
   obs->AddObserver(this, kSmsDeliverySuccessObserverTopic, false);
   obs->AddObserver(this, kSmsDeliveryErrorObserverTopic, false);
+  obs->AddObserver(this, kSmsReadSuccessObserverTopic, false);
+  obs->AddObserver(this, kSmsReadErrorObserverTopic, false);
 }
 
 void
 MobileMessageManager::Shutdown()
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   // GetObserverService() can return null is some situations like shutdown.
   if (!obs) {
@@ -94,16 +100,18 @@ MobileMessageManager::Shutdown()
 
   obs->RemoveObserver(this, kSmsReceivedObserverTopic);
   obs->RemoveObserver(this, kSmsRetrievingObserverTopic);
   obs->RemoveObserver(this, kSmsSendingObserverTopic);
   obs->RemoveObserver(this, kSmsSentObserverTopic);
   obs->RemoveObserver(this, kSmsFailedObserverTopic);
   obs->RemoveObserver(this, kSmsDeliverySuccessObserverTopic);
   obs->RemoveObserver(this, kSmsDeliveryErrorObserverTopic);
+  obs->RemoveObserver(this, kSmsReadSuccessObserverTopic);
+  obs->RemoveObserver(this, kSmsReadErrorObserverTopic);
 }
 
 NS_IMETHODIMP
 MobileMessageManager::GetSegmentInfoForText(const nsAString& aText,
                                             nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
   NS_ENSURE_TRUE(smsService, NS_ERROR_FAILURE);
@@ -531,16 +539,24 @@ MobileMessageManager::Observe(nsISupport
   if (!strcmp(aTopic, kSmsDeliverySuccessObserverTopic)) {
     return DispatchTrustedSmsEventToSelf(aTopic, DELIVERY_SUCCESS_EVENT_NAME, aSubject);
   }
 
   if (!strcmp(aTopic, kSmsDeliveryErrorObserverTopic)) {
     return DispatchTrustedSmsEventToSelf(aTopic, DELIVERY_ERROR_EVENT_NAME, aSubject);
   }
 
+  if (!strcmp(aTopic, kSmsReadSuccessObserverTopic)) {
+    return DispatchTrustedSmsEventToSelf(aTopic, READ_SUCCESS_EVENT_NAME, aSubject);
+  }
+
+  if (!strcmp(aTopic, kSmsReadErrorObserverTopic)) {
+    return DispatchTrustedSmsEventToSelf(aTopic, READ_ERROR_EVENT_NAME, aSubject);
+  }
+
   return NS_OK;
 }
 
 NS_IMETHODIMP
 MobileMessageManager::GetSmscAddress(uint32_t aServiceId, uint8_t aArgc,
                                      nsIDOMDOMRequest** aRequest)
 {
   nsCOMPtr<nsISmsService> smsService = do_GetService(SMS_SERVICE_CONTRACTID);
--- a/dom/mobilemessage/src/Types.h
+++ b/dom/mobilemessage/src/Types.h
@@ -33,16 +33,26 @@ enum DeliveryStatus {
   eDeliveryStatus_Pending,
   eDeliveryStatus_Error,
   eDeliveryStatus_Reject,
   eDeliveryStatus_Manual,
   // This state should stay at the end.
   eDeliveryStatus_EndGuard
 };
 
+// For MmsMessageData.readStatus.
+enum ReadStatus {
+  eReadStatus_NotApplicable = 0,
+  eReadStatus_Success,
+  eReadStatus_Pending,
+  eReadStatus_Error,
+  // This state should stay at the end.
+  eReadStatus_EndGuard
+};
+
 // For {Mms,Sms}FilterData.read.
 enum ReadState {
   eReadState_Unknown = -1,
   eReadState_Unread,
   eReadState_Read,
   // This state should stay at the end.
   eReadState_EndGuard
 };
@@ -88,16 +98,26 @@ struct ParamTraits<mozilla::dom::mobilem
 template <>
 struct ParamTraits<mozilla::dom::mobilemessage::DeliveryStatus>
   : public EnumSerializer<mozilla::dom::mobilemessage::DeliveryStatus,
                           mozilla::dom::mobilemessage::eDeliveryStatus_NotApplicable,
                           mozilla::dom::mobilemessage::eDeliveryStatus_EndGuard>
 {};
 
 /**
+ * Read status serializer.
+ */
+template <>
+struct ParamTraits<mozilla::dom::mobilemessage::ReadStatus>
+  : public EnumSerializer<mozilla::dom::mobilemessage::ReadStatus,
+                          mozilla::dom::mobilemessage::eReadStatus_NotApplicable,
+                          mozilla::dom::mobilemessage::eReadStatus_EndGuard>
+{};
+
+/**
  * Read state serializer.
  */
 template <>
 struct ParamTraits<mozilla::dom::mobilemessage::ReadState>
   : public EnumSerializer<mozilla::dom::mobilemessage::ReadState,
                           mozilla::dom::mobilemessage::eReadState_Unknown,
                           mozilla::dom::mobilemessage::eReadState_EndGuard>
 {};
--- a/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
+++ b/dom/mobilemessage/src/gonk/MmsPduHelper.jsm
@@ -40,16 +40,58 @@ this.translatePduErrorToStatus = functio
 
 function defineLazyRegExp(obj, name, pattern) {
   obj.__defineGetter__(name, function() {
     delete obj[name];
     return obj[name] = new RegExp(pattern);
   });
 }
 
+function RangedValue(name, min, max) {
+  this.name = name;
+  this.min = min;
+  this.max = max;
+}
+RangedValue.prototype = {
+  name: null,
+  min: null,
+  max: null,
+
+  /**
+   * @param data
+   *        A wrapped object containing raw PDU data.
+   *
+   * @return A decoded integer.
+   *
+   * @throws CodeError if decoded value is not in the range [this.min, this.max].
+   */
+  decode: function decode(data) {
+    let value = WSP.Octet.decode(data);
+    if ((value >= this.min) && (value <= this.max)) {
+      return value;
+    }
+
+    throw new WSP.CodeError(this.name + ": invalid value " + value);
+  },
+
+  /**
+   * @param data
+   *        A wrapped object to store encoded raw data.
+   * @param value
+   *        An integer value within thr range [this.min, this.max].
+   */
+  encode: function encode(data, value) {
+    if ((value < this.min) || (value > this.max)) {
+      throw new WSP.CodeError(this.name + ": invalid value " + value);
+    }
+
+    WSP.Octet.encode(data, value);
+  },
+};
+
 /**
  * Internal decoding function for boolean values.
  *
  * Boolean-value = Yes | No
  * Yes = <Octet 128>
  * No = <Octet 129>
  */
 this.BooleanValue = {
@@ -333,53 +375,30 @@ this.MmsHeader = {
     }
 
     WSP.ShortInteger.encode(data, entry.number);
     entry.coder.encode(data, header.value);
   },
 };
 
 /**
+ * Cancel-status-value = Cancel Request Successfully received |
+ *                       Cancel Request corrupted
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.7
+ */
+this.CancelStatusValue = new RangedValue("Cancel-status-value", 128, 129);
+
+/**
  * Content-class-value = text | image-basic| image-rich | video-basic |
  *                       video-rich | megapixel | content-basic | content-rich
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.9
  */
-this.ContentClassValue = {
-  /**
-   * @param data
-   *        A wrapped object containing raw PDU data.
-   *
-   * @return A integer value for each class.
-   *
-   * @throws CodeError if decoded value is not in range 128..135.
-   */
-  decode: function decode(data) {
-    let value = WSP.Octet.decode(data);
-    if ((value >= 128) && (value <= 135)) {
-      return value;
-    }
-
-    throw new WSP.CodeError("Content-class-value: invalid class " + value);
-  },
-
-  /**
-   * @param data
-   *        A wrapped object to store encoded raw data.
-   * @param value
-   *        A numeric content class value to be encoded.
-   */
-  encode: function encode(data, value) {
-    if ((value < 128) || (value > 135)) {
-      throw new WSP.CodeError("Content-class-value: invalid class " + value);
-    }
-
-    WSP.Octet.encode(data, value);
-  },
-};
+this.ContentClassValue = new RangedValue("Content-class-value", 128, 135);
 
 /**
  * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
  *
  *   Content-location-value = Uri-value
  *
  * When used in the M-Mbox-Delete.conf and M-Delete.conf PDU:
  *
@@ -964,50 +983,17 @@ this.MessageClassValue = {
   },
 };
 
  /**
  * Message-type-value = <Octet 128..151>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.30
  */
-this.MessageTypeValue = {
-  /**
-   * @param data
-   *        A wrapped object containing raw PDU data.
-   *
-   * @return A decoded integer.
-   *
-   * @throws CodeError if decoded value is not in the range 128..151.
-   */
-  decode: function decode(data) {
-    let type = WSP.Octet.decode(data);
-    if ((type >= 128) && (type <= 151)) {
-      return type;
-    }
-
-    throw new WSP.CodeError("Message-type-value: invalid type " + type);
-  },
-
-  /**
-   * @param data
-   *        A wrapped object to store encoded raw data.
-   * @param type
-   *        A numeric message type value to be encoded.
-   *
-   * @throws CodeError if the value is not in the range 128..151.
-   */
-  encode: function encode(data, type) {
-    if ((type < 128) || (type > 151)) {
-      throw new WSP.CodeError("Message-type-value: invalid type " + type);
-    }
-
-    WSP.Octet.encode(data, type);
-  },
-};
+this.MessageTypeValue = new RangedValue("Message-type-value", 128, 151);
 
 /**
  * MM-flags-value = Value-length ( Add-token | Remove-token | Filter-token ) Encoded-string-value
  * Add-token = <Octet 128>
  * Remove-token = <Octet 129>
  * Filter-token = <Octet 130>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.32
@@ -1070,91 +1056,34 @@ this.MmFlagsValue = {
  * Draft = <Octet 128>
  * Sent = <Octet 129>
  * New = <Octet 130>
  * Retrieved = <Octet 131>
  * Forwarded = <Octet 132>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.33
  */
-this.MmStateValue = {
-  /**
-   * @param data
-   *        A wrapped object containing raw PDU data.
-   *
-   * @return A decoded integer.
-   *
-   * @throws CodeError if decoded value is not in the range 128..132.
-   */
-  decode: function decode(data) {
-    let state = WSP.Octet.decode(data);
-    if ((state >= 128) && (state <= 132)) {
-      return state;
-    }
-
-    throw new WSP.CodeError("MM-state-value: invalid state " + state);
-  },
-
-  /**
-   * @param data
-   *        A wrapped object to store encoded raw data.
-   * @param state
-   *        A numeric state value to be encoded.
-   *
-   * @throws CodeError if state is not in the range 128..132.
-   */
-  encode: function encode(data, state) {
-    if ((state < 128) || (state > 132)) {
-      throw new WSP.CodeError("MM-state-value: invalid state " + state);
-    }
-
-    WSP.Octet.encode(data, state);
-  },
-};
+this.MmStateValue = new RangedValue("MM-state-value", 128, 132);
 
 /**
  * Priority-value = Low | Normal | High
  * Low = <Octet 128>
  * Normal = <Octet 129>
  * High = <Octet 130>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.35
  */
-this.PriorityValue = {
-  /**
-   * @param data
-   *        A wrapped object containing raw PDU data.
-   *
-   * @return A decoded integer.
-   *
-   * @throws CodeError if decoded value is not in the range 128..130.
-   */
-  decode: function decode(data) {
-    let priority = WSP.Octet.decode(data);
-    if ((priority >= 128) && (priority <= 130)) {
-      return priority;
-    }
+this.PriorityValue = new RangedValue("Priority-value", 128, 130);
 
-    throw new WSP.CodeError("Priority-value: invalid priority " + priority);
-  },
-
-  /**
-   * @param data
-   *        A wrapped object to store encoded raw data.
-   * @param priority
-   *        A numeric priority value to be encoded.
-   */
-  encode: function encode(data, priority) {
-    if ((priority < 128) || (priority > 130)) {
-      throw new WSP.CodeError("Priority-value: invalid priority " + priority);
-    }
-
-    WSP.Octet.encode(data, priority);
-  },
-};
+/**
+ * Read-status-value = Read | Deleted without being read
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.38
+ */
+this.ReadStatusValue = new RangedValue("Read-status-value", 128, 129);
 
 /**
  * Recommended-Retrieval-Mode-value = Manual
  * Manual = <Octet 128>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.39
  */
 this.RecommendedRetrievalModeValue = {
@@ -1174,48 +1103,17 @@ this.RecommendedRetrievalModeValue = {
  *                        Accepted text only
  * Requested = <Octet 128>
  * Requested text only = <Octet 129>
  * Accepted = <Octet 130>
  * Accepted text only = <Octet 131>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.43
  */
-this.ReplyChargingValue = {
-  /**
-   * @param data
-   *        A wrapped object containing raw PDU data.
-   *
-   * @return A decoded integer.
-   *
-   * @throws CodeError if decoded value is not in the range 128..131.
-   */
-  decode: function decode(data) {
-    let value = WSP.Octet.decode(data);
-    if ((value >= 128) && (value <= 131)) {
-      return value;
-    }
-
-    throw new WSP.CodeError("Reply-charging-value: invalid value " + value);
-  },
-
-  /**
-   * @param data
-   *        A wrapped object to store encoded raw data.
-   * @param value
-   *        An integer value within thr range 128..131.
-   */
-  encode: function encode(data, value) {
-    if ((value < 128) || (value > 131)) {
-      throw new WSP.CodeError("Reply-charging-value: invalid value " + value);
-    }
-
-    WSP.Octet.encode(data, value);
-  },
-};
+this.ReplyChargingValue = new RangedValue("Reply-charging-value", 128, 131);
 
 /**
  * When used in a PDU other than M-Mbox-Delete.conf and M-Delete.conf:
  *
  *   Response-text-value = Encoded-string-value
  *
  * When used in the M-Mbox-Delete.conf and M-Delete.conf PDUs:
  *
@@ -1295,63 +1193,37 @@ this.RetrieveStatusValue = {
     // Any other values SHALL NOT be used. They are reserved for future use.
     // An MMS Client that receives such a reserved value MUST react the same
     // as it does to the value 224 (Error-permanent-failure).
     return MMS_PDU_ERROR_PERMANENT_FAILURE;
   },
 };
 
 /**
+ * Sender-visibility-value = Hide | Show
+ *
+ * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.52
+ */
+this.SenderVisibilityValue = new RangedValue("Sender-visibility-value", 128, 129);
+
+/**
  * Status-value = Expired | Retrieved | Rejected | Deferred | Unrecognised |
  *                Indeterminate | Forwarded | Unreachable
  * Expired = <Octet 128>
  * Retrieved = <Octet 129>
  * Rejected = <Octet 130>
  * Deferred = <Octet 131>
  * Unrecognised = <Octet 132>
  * Indeterminate = <Octet 133>
  * Forwarded = <Octet 134>
  * Unreachable = <Octet 135>
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.54
  */
-this.StatusValue = {
-  /**
-   * @param data
-   *        A wrapped object containing raw PDU data.
-   *
-   * @return A decoded integer.
-   *
-   * @throws CodeError if decoded value is not in the range 128..135.
-   */
-  decode: function decode(data) {
-    let status = WSP.Octet.decode(data);
-    if ((status >= 128) && (status <= 135)) {
-      return status;
-    }
-
-    throw new WSP.CodeError("Status-value: invalid status " + status);
-  },
-
-  /**
-   * @param data
-   *        A wrapped object to store encoded raw data.
-   * @param value
-   *        A numeric status value to be encoded.
-   *
-   * @throws CodeError if the value is not in the range 128..135.
-   */
-  encode: function encode(data, value) {
-    if ((value < 128) || (value > 135)) {
-      throw new WSP.CodeError("Status-value: invalid status " + value);
-    }
-
-    WSP.Octet.encode(data, value);
-  },
-};
+this.StatusValue = new RangedValue("Status-value", 128, 135);
 
 this.PduHelper = {
   /**
    * @param data
    *        A wrapped object containing raw PDU data.
    * @param headers
    *        An optional object to store parsed header fields. Created
    *        automatically if undefined.
@@ -1627,16 +1499,23 @@ const MMS_PDU_TYPES = (function () {
                                             "x-mms-transaction-id",
                                             "x-mms-mms-version"]);
   add(MMS_PDU_TYPE_READ_REC_IND, false, ["x-mms-message-type",
                                          "message-id",
                                          "x-mms-mms-version",
                                          "to",
                                          "from",
                                          "x-mms-read-status"]);
+  add(MMS_PDU_TYPE_READ_ORIG_IND, false, ["x-mms-message-type",
+                                          "x-mms-mms-version",
+                                          "message-id",
+                                          "to",
+                                          "from",
+                                          "date",
+                                          "x-mms-read-status"]);
 
   return pdus;
 })();
 
 /**
  * Header field names and assigned numbers.
  *
  * @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.4
@@ -1666,24 +1545,24 @@ const MMS_HEADER_FIELDS = (function () {
   add("x-mms-message-type",                      0x0C, MessageTypeValue);
   add("x-mms-mms-version",                       0x0D, WSP.ShortInteger);
   add("x-mms-message-size",                      0x0E, WSP.LongInteger);
   add("x-mms-priority",                          0x0F, PriorityValue);
   add("x-mms-read-report",                       0x10, BooleanValue);
   add("x-mms-report-allowed",                    0x11, BooleanValue);
   add("x-mms-response-status",                   0x12, RetrieveStatusValue);
   add("x-mms-response-text",                     0x13, ResponseText);
-  add("x-mms-sender-visibility",                 0x14, BooleanValue);
+  add("x-mms-sender-visibility",                 0x14, SenderVisibilityValue);
   add("x-mms-status",                            0x15, StatusValue);
   add("subject",                                 0x16, EncodedStringValue);
   add("to",                                      0x17, Address);
   add("x-mms-transaction-id",                    0x18, WSP.TextString);
   add("x-mms-retrieve-status",                   0x19, RetrieveStatusValue);
   add("x-mms-retrieve-text",                     0x1A, EncodedStringValue);
-  add("x-mms-read-status",                       0x1B, BooleanValue);
+  add("x-mms-read-status",                       0x1B, ReadStatusValue);
   add("x-mms-reply-charging",                    0x1C, ReplyChargingValue);
   add("x-mms-reply-charging-deadline",           0x1D, ExpiryValue);
   add("x-mms-reply-charging-id",                 0x1E, WSP.TextString);
   add("x-mms-reply-charging-size",               0x1F, WSP.LongInteger);
   add("x-mms-previously-sent-by",                0x20, PreviouslySentByValue);
   add("x-mms-previously-sent-date",              0x21, PreviouslySentDateValue);
   add("x-mms-store",                             0x22, BooleanValue);
   add("x-mms-mm-state",                          0x23, MmStateValue);
@@ -1709,17 +1588,17 @@ const MMS_HEADER_FIELDS = (function () {
   add("x-mms-applic-id",                         0x37, WSP.TextString);
   add("x-mms-reply-applic-id",                   0x38, WSP.TextString);
   add("x-mms-aux-applic-id",                     0x39, WSP.TextString);
   add("x-mms-content-class",                     0x3A, ContentClassValue);
   add("x-mms-drm-content",                       0x3B, BooleanValue);
   add("x-mms-adaptation-allowed",                0x3C, BooleanValue);
   add("x-mms-replace-id",                        0x3D, WSP.TextString);
   add("x-mms-cancel-id",                         0x3E, WSP.TextString);
-  add("x-mms-cancel-status",                     0x3F, BooleanValue);
+  add("x-mms-cancel-status",                     0x3F, CancelStatusValue);
 
   return names;
 })();
 
 // @see OMA-TS-MMS_ENC-V1_3-20110913-A Table 27: Parameter Name Assignments
 const MMS_WELL_KNOWN_PARAMS = (function () {
   let params = {};
 
@@ -1754,32 +1633,35 @@ this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOL
   // Utility functions
   "translatePduErrorToStatus",
 
   // Decoders
   "BooleanValue",
   "Address",
   "HeaderField",
   "MmsHeader",
+  "CancelStatusValue",
   "ContentClassValue",
   "ContentLocationValue",
   "ElementDescriptorValue",
   "Parameter",
   "EncodedStringValue",
   "ExpiryValue",
   "FromValue",
   "PreviouslySentByValue",
   "PreviouslySentDateValue",
   "MessageClassValue",
   "MessageTypeValue",
   "MmFlagsValue",
   "MmStateValue",
   "PriorityValue",
+  "ReadStatusValue",
   "RecommendedRetrievalModeValue",
   "ReplyChargingValue",
   "ResponseText",
   "RetrieveStatusValue",
+  "SenderVisibilityValue",
   "StatusValue",
 
   // Parser
   "PduHelper",
 ]);
 
--- a/dom/mobilemessage/src/gonk/MmsService.js
+++ b/dom/mobilemessage/src/gonk/MmsService.js
@@ -13,30 +13,35 @@ Cu.import("resource://gre/modules/Servic
 
 Cu.import("resource://gre/modules/NetUtil.jsm");
 Cu.import("resource://gre/modules/PhoneNumberUtils.jsm");
 
 const RIL_MMSSERVICE_CONTRACTID = "@mozilla.org/mms/rilmmsservice;1";
 const RIL_MMSSERVICE_CID = Components.ID("{217ddd76-75db-4210-955d-8806cd8d87f9}");
 
 let DEBUG = false;
+function debug(s) {
+  dump("-@- MmsService: " + s + "\n");
+};
 
 // Read debug setting from pref.
 try {
   let debugPref = Services.prefs.getBoolPref("mms.debugging.enabled");
   DEBUG = DEBUG || debugPref;
 } catch (e) {}
 
 const kSmsSendingObserverTopic           = "sms-sending";
 const kSmsSentObserverTopic              = "sms-sent";
 const kSmsFailedObserverTopic            = "sms-failed";
 const kSmsReceivedObserverTopic          = "sms-received";
 const kSmsRetrievingObserverTopic        = "sms-retrieving";
 const kSmsDeliverySuccessObserverTopic   = "sms-delivery-success";
 const kSmsDeliveryErrorObserverTopic     = "sms-delivery-error";
+const kSmsReadSuccessObserverTopic       = "sms-read-success";
+const kSmsReadErrorObserverTopic         = "sms-read-error";
 
 const NS_XPCOM_SHUTDOWN_OBSERVER_ID      = "xpcom-shutdown";
 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
 const kMobileMessageDeletedObserverTopic = "mobile-message-deleted";
 
 const kPrefRilRadioDisabled              = "ril.radio.disabled";
 
 // HTTP status codes:
@@ -1349,17 +1354,17 @@ function ReadRecTransaction(mmsConnectio
   headers["x-mms-message-type"] = MMS.MMS_PDU_TYPE_READ_REC_IND;
   headers["x-mms-mms-version"] = MMS.MMS_VERSION;
   headers["message-id"] = messageID;
   let type = MMS.Address.resolveType(toAddress);
   let to = {address: toAddress,
             type: type}
   headers["to"] = to;
   headers["from"] = null;
-  headers["x-mms-read-status"] = true;
+  headers["x-mms-read-status"] = MMS.MMS_PDU_READ_STATUS_READ;
 
   this.istream = MMS.PduHelper.compose(null, {headers: headers});
   if (!this.istream) {
     throw Cr.NS_ERROR_FAILURE;
   }
 }
 ReadRecTransaction.prototype = {
   run: function() {
@@ -1426,48 +1431,43 @@ MmsService.prototype = {
    * @param intermediate
    *        Intermediate MMS message parsed from PDU.
    */
   convertIntermediateToSavable: function convertIntermediateToSavable(mmsConnection,
                                                                       intermediate,
                                                                       retrievalMode) {
     intermediate.type = "mms";
     intermediate.delivery = DELIVERY_NOT_DOWNLOADED;
-    // As a receiver, we don't need to care about the delivery status of others.
-    let deliveryInfo = intermediate.deliveryInfo = [{
-      receiver: mmsConnection.getPhoneNumber(),
-      deliveryStatus: DELIVERY_STATUS_NOT_APPLICABLE }];
 
+    let deliveryStatus;
     switch (retrievalMode) {
       case RETRIEVAL_MODE_MANUAL:
-        deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_MANUAL;
+        deliveryStatus = DELIVERY_STATUS_MANUAL;
         break;
       case RETRIEVAL_MODE_NEVER:
-        deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_REJECTED;
+        deliveryStatus = DELIVERY_STATUS_REJECTED;
         break;
       case RETRIEVAL_MODE_AUTOMATIC:
-        deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_PENDING;
+        deliveryStatus = DELIVERY_STATUS_PENDING;
         break;
       case RETRIEVAL_MODE_AUTOMATIC_HOME:
         if (mmsConnection.isVoiceRoaming()) {
-          deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_MANUAL;
+          deliveryStatus = DELIVERY_STATUS_MANUAL;
         } else {
-          deliveryInfo[0].deliveryStatus = DELIVERY_STATUS_PENDING;
+          deliveryStatus = DELIVERY_STATUS_PENDING;
         }
         break;
+      default:
+        deliveryStatus = DELIVERY_STATUS_NOT_APPLICABLE;
+        break;
     }
+    // |intermediate.deliveryStatus| will be deleted after being stored in db.
+    intermediate.deliveryStatus = deliveryStatus;
 
     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 {
-      intermediate.sender = "anonymous";
-    }
     intermediate.receivers = [];
     intermediate.phoneNumber = mmsConnection.getPhoneNumber();
     intermediate.iccId = mmsConnection.getIccId();
     return intermediate;
   },
 
   /**
    * Merge the retrieval confirmation into the savable message.
@@ -1480,40 +1480,33 @@ MmsService.prototype = {
    * @param savable
    *        The indexedDB savable MMS message, which is going to be
    *        merged with the extra retrieval confirmation.
    */
   mergeRetrievalConfirmation: function mergeRetrievalConfirmation(mmsConnection,
                                                                   intermediate,
                                                                   savable) {
     savable.timestamp = Date.now();
-    if (intermediate.headers.from) {
-      savable.sender = intermediate.headers.from.address;
-    } else {
-      savable.sender = "anonymous";
-    }
     savable.receivers = [];
     // We don't have Bcc in recevied MMS message.
     for each (let type in ["cc", "to"]) {
       if (intermediate.headers[type]) {
         if (intermediate.headers[type] instanceof Array) {
           for (let index in intermediate.headers[type]) {
             savable.receivers.push(intermediate.headers[type][index].address);
           }
         } else {
           savable.receivers.push(intermediate.headers[type].address);
         }
       }
     }
 
     savable.delivery = DELIVERY_RECEIVED;
-    // As a receiver, we don't need to care about the delivery status of others.
-    savable.deliveryInfo = [{
-      receiver: mmsConnection.getPhoneNumber(),
-      deliveryStatus: DELIVERY_STATUS_SUCCESS }];
+    // |savable.deliveryStatus| will be deleted after being stored in db.
+    savable.deliveryStatus = DELIVERY_STATUS_SUCCESS;
     for (let field in intermediate.headers) {
       savable.headers[field] = intermediate.headers[field];
     }
     if (intermediate.parts) {
       savable.parts = intermediate.parts;
     }
     if (intermediate.content) {
       savable.content = intermediate.content;
@@ -1610,16 +1603,18 @@ MmsService.prototype = {
    */
   retrieveMessageCallback: function retrieveMessageCallback(mmsConnection,
                                                             wish,
                                                             savableMessage,
                                                             mmsStatus,
                                                             retrievedMessage) {
     if (DEBUG) debug("retrievedMessage = " + JSON.stringify(retrievedMessage));
 
+    let transactionId = savableMessage.headers["x-mms-transaction-id"];
+
     // 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,
@@ -1649,18 +1644,16 @@ MmsService.prototype = {
         this.broadcastReceivedMessageEvent(domMessage);
       }).bind(this));
       return;
     }
 
     savableMessage = this.mergeRetrievalConfirmation(mmsConnection,
                                                      retrievedMessage,
                                                      savableMessage);
-    let transactionId = savableMessage.headers["x-mms-transaction-id"];
-
     gMobileMessageDatabaseService.saveReceivedMessage(savableMessage,
         (function (rv, domMessage) {
       let success = Components.isSuccessCode(rv);
 
       // Cite 6.2.1 "Transaction Flow" in OMA-TS-MMS_ENC-V1_3-20110913-A:
       // The M-NotifyResp.ind response PDU SHALL provide a message retrieval
       // status code. The status ‘retrieved’ SHALL be used only if the MMS
       // Client has successfully retrieved the MM prior to sending the
@@ -1800,21 +1793,16 @@ MmsService.prototype = {
 
   /**
    * Handle incoming M-Delivery.ind PDU.
    *
    * @param aMsg
    *        The MMS message object.
    */
   handleDeliveryIndication: function handleDeliveryIndication(aMsg) {
-    if (DEBUG) {
-      debug("handleDeliveryIndication: got delivery report" +
-            JSON.stringify(aMsg));
-    }
-
     let headers = aMsg.headers;
     let envelopeId = headers["message-id"];
     let address = headers.to.address;
     let mmsStatus = headers["x-mms-status"];
     if (DEBUG) {
       debug("Start updating the delivery status for envelopeId: " + envelopeId +
             " address: " + address + " mmsStatus: " + mmsStatus);
     }
@@ -1842,21 +1830,18 @@ MmsService.prototype = {
         break;
       default:
         if (DEBUG) debug("Cannot handle this MMS status. Returning.");
         return;
     }
 
     if (DEBUG) debug("Updating the delivery status to: " + deliveryStatus);
     gMobileMessageDatabaseService
-      .setMessageDeliveryByEnvelopeId(envelopeId,
-                                      address,
-                                      null,
-                                      deliveryStatus,
-                                      (function notifySetDeliveryResult(aRv, aDomMessage) {
+      .setMessageDeliveryStatusByEnvelopeId(envelopeId, address, deliveryStatus,
+                                            (function(aRv, aDomMessage) {
       if (DEBUG) debug("Marking the delivery status is done.");
       // TODO bug 832140 handle !Components.isSuccessCode(aRv)
 
       let topic;
       if (mmsStatus === MMS.MMS_PDU_STATUS_RETRIEVED) {
         topic = kSmsDeliverySuccessObserverTopic;
 
         // Broadcasting a 'sms-delivery-success' system message to open apps.
@@ -1869,16 +1854,67 @@ MmsService.prototype = {
       }
 
       // Notifying observers the delivery status is updated.
       Services.obs.notifyObservers(aDomMessage, topic, null);
     }).bind(this));
   },
 
   /**
+   * Handle incoming M-Read-Orig.ind PDU.
+   *
+   * @param aIndication
+   *        The MMS message object.
+   */
+  handleReadOriginateIndication:
+    function handleReadOriginateIndication(aIndication) {
+
+    let headers = aIndication.headers;
+    let envelopeId = headers["message-id"];
+    let address = headers.from.address;
+    let mmsReadStatus = headers["x-mms-read-status"];
+    if (DEBUG) {
+      debug("Start updating the read status for envelopeId: " + envelopeId +
+            ", address: " + address + ", mmsReadStatus: " + mmsReadStatus);
+    }
+
+    // From OMA-TS-MMS_ENC-V1_3-20110913-A subclause 9.4 "X-Mms-Read-Status",
+    // in M-Read-Rec-Orig.ind the X-Mms-Read-Status could be
+    // MMS.MMS_READ_STATUS_{ READ, DELETED_WITHOUT_BEING_READ }.
+    let readStatus = mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ
+                   ? MMS.DOM_READ_STATUS_SUCCESS
+                   : MMS.DOM_READ_STATUS_ERROR;
+    if (DEBUG) debug("Updating the read status to: " + readStatus);
+
+    gMobileMessageDatabaseService
+      .setMessageReadStatusByEnvelopeId(envelopeId, address, readStatus,
+                                        (function(aRv, aDomMessage) {
+      if (!Components.isSuccessCode(aRv)) {
+        // Notifying observers the read status is error.
+        Services.obs.notifyObservers(aDomMessage, kSmsReadSuccessObserverTopic, null);
+        return;
+      }
+
+      if (DEBUG) debug("Marking the read status is done.");
+      let topic;
+      if (mmsReadStatus == MMS.MMS_PDU_READ_STATUS_READ) {
+        topic = kSmsReadSuccessObserverTopic;
+
+        // Broadcasting a 'sms-read-success' system message to open apps.
+        this.broadcastMmsSystemMessage(topic, aDomMessage);
+      } else {
+        topic = kSmsReadErrorObserverTopic;
+      }
+
+      // Notifying observers the read status is updated.
+      Services.obs.notifyObservers(aDomMessage, topic, null);
+    }).bind(this));
+  },
+
+  /**
    * A utility function to convert the MmsParameters dictionary object
    * to a database-savable message.
    *
    * @param aMmsConnection
    *        The MMS connection.
    * @param aParams
    *        The MmsParameters dictionay object.
    * @param aMessage (output)
@@ -2108,50 +2144,48 @@ MmsService.prototype = {
                           function notifySendingResult(aRv, aDomMessage) {
       if (DEBUG) debug("Saving sending message is done. Start to send.");
 
       // TODO bug 832140 handle !Components.isSuccessCode(aRv)
       Services.obs.notifyObservers(aDomMessage, kSmsSendingObserverTopic, null);
 
       if (errorCode !== Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR) {
         if (DEBUG) debug("Error! The params for sending MMS are invalid.");
-        sendTransactionCb(aDomMessage, errorCode);
+        sendTransactionCb(aDomMessage, errorCode, null);
         return;
       }
 
       // This is the entry point starting to send MMS.
       let sendTransaction;
       try {
         sendTransaction =
           new SendTransaction(mmsConnection, aDomMessage.id, savableMessage,
                               savableMessage["deliveryStatusRequested"]);
       } catch (e) {
         if (DEBUG) debug("Exception: fail to create a SendTransaction instance.");
         sendTransactionCb(aDomMessage,
-                          Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
+                          Ci.nsIMobileMessageCallback.INTERNAL_ERROR, null);
         return;
       }
       sendTransaction.run(function callback(aMmsStatus, aMsg) {
         if (DEBUG) debug("The sending status of sendTransaction.run(): " + aMmsStatus);
         let errorCode;
         if (aMmsStatus == _MMS_ERROR_MESSAGE_DELETED) {
           errorCode = Ci.nsIMobileMessageCallback.NOT_FOUND_ERROR;
         } else if (aMmsStatus == _MMS_ERROR_RADIO_DISABLED) {
           errorCode = Ci.nsIMobileMessageCallback.RADIO_DISABLED_ERROR;
         } else if (aMmsStatus == _MMS_ERROR_NO_SIM_CARD) {
           errorCode = Ci.nsIMobileMessageCallback.NO_SIM_CARD_ERROR;
         } else if (aMmsStatus != MMS.MMS_PDU_ERROR_OK) {
           errorCode = Ci.nsIMobileMessageCallback.INTERNAL_ERROR;
         } else {
           errorCode = Ci.nsIMobileMessageCallback.SUCCESS_NO_ERROR;
         }
-        let envelopeId = null;
-        if (aMsg) {
-          envelopeId = aMsg.headers ? aMsg.headers["message-id"] : null;
-        }
+        let envelopeId =
+          aMsg && aMsg.headers && aMsg.headers["message-id"] || null;
         sendTransactionCb(aDomMessage, errorCode, envelopeId);
       });
     });
   },
 
   retrieve: function retrieve(aMessageId, aRequest) {
     if (DEBUG) debug("Retrieving message with ID " + aMessageId);
     gMobileMessageDatabaseService.getMessageRecordById(aMessageId,
@@ -2176,17 +2210,18 @@ MmsService.prototype = {
         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
       if (DELIVERY_NOT_DOWNLOADED != aMessageRecord.delivery) {
         if (DEBUG) debug("Delivery of message record is not 'not-downloaded'.");
         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
-      if (DELIVERY_STATUS_PENDING == aMessageRecord.deliveryStatus) {
+      let deliveryStatus = aMessageRecord.deliveryInfo[0].deliveryStatus;
+      if (DELIVERY_STATUS_PENDING == deliveryStatus) {
         if (DEBUG) debug("Delivery status of message record is 'pending'.");
         aRequest.notifyGetMessageFailed(Ci.nsIMobileMessageCallback.INTERNAL_ERROR);
         return;
       }
 
       // Cite 6.2 "Multimedia Message Notification" in OMA-TS-MMS_ENC-V1_3-20110913-A:
       // The field has only one format, relative. The recipient client calculates
       // this length of time relative to the time it receives the notification.
@@ -2370,16 +2405,19 @@ MmsService.prototype = {
 
     switch (msg.type) {
       case MMS.MMS_PDU_TYPE_NOTIFICATION_IND:
         this.handleNotificationIndication(options.serviceId, msg);
         break;
       case MMS.MMS_PDU_TYPE_DELIVERY_IND:
         this.handleDeliveryIndication(msg);
         break;
+      case MMS.MMS_PDU_TYPE_READ_ORIG_IND:
+        this.handleReadOriginateIndication(msg);
+        break;
       default:
         if (DEBUG) debug("Unsupported X-MMS-Message-Type: " + msg.type);
         break;
     }
   },
 
   // nsIObserver
 
@@ -2390,17 +2428,8 @@ MmsService.prototype = {
           this.mmsDefaultServiceId = getDefaultServiceId();
         }
         break;
     }
   }
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([MmsService]);
-
-let debug;
-if (DEBUG) {
-  debug = function (s) {
-    dump("-@- MmsService: " + s + "\n");
-  };
-} else {
-  debug = function (s) {};
-}
--- a/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
+++ b/dom/mobilemessage/src/gonk/MobileMessageDatabaseService.js
@@ -23,17 +23,17 @@ const RIL_GETMESSAGESCURSOR_CID =
 const RIL_GETTHREADSCURSOR_CID =
   Components.ID("{95ee7c3e-d6f2-4ec4-ade5-0c453c036d35}");
 
 const DEBUG = false;
 const DISABLE_MMS_GROUPING_FOR_RECEIVING = true;
 
 
 const DB_NAME = "sms";
-const DB_VERSION = 19;
+const DB_VERSION = 20;
 const MESSAGE_STORE_NAME = "sms";
 const THREAD_STORE_NAME = "thread";
 const PARTICIPANT_STORE_NAME = "participant";
 const MOST_RECENT_STORE_NAME = "most-recent";
 
 const DELIVERY_SENDING = "sending";
 const DELIVERY_SENT = "sent";
 const DELIVERY_RECEIVED = "received";
@@ -255,16 +255,20 @@ MobileMessageDatabaseService.prototype =
             if (DEBUG) debug("Upgrade to version 18. Add last message subject into threadRecord.");
             self.upgradeSchema17(event.target.transaction, next);
             break;
           case 18:
             if (DEBUG) debug("Upgrade to version 19. Add pid for incoming SMS.");
             self.upgradeSchema18(event.target.transaction, next);
             break;
           case 19:
+            if (DEBUG) debug("Upgrade to version 20. Add readStatus and readTimestamp.");
+            self.upgradeSchema19(event.target.transaction, next);
+            break;
+          case 20:
             // This will need to be moved for each new version
             if (DEBUG) debug("Upgrade finished.");
             break;
           default:
             event.target.transaction.abort();
             callback("Old database version: " + event.oldVersion, null);
             break;
         }
@@ -1176,16 +1180,73 @@ MobileMessageDatabaseService.prototype =
       if (messageRecord.type == "sms") {
         messageRecord.pid = RIL.PDU_PID_DEFAULT;
         cursor.update(messageRecord);
       }
       cursor.continue();
     };
   },
 
+  /**
+   * Add readStatus and readTimestamp.
+   */
+  upgradeSchema19: function upgradeSchema19(transaction, next) {
+    let messageStore = transaction.objectStore(MESSAGE_STORE_NAME);
+    messageStore.openCursor().onsuccess = function(event) {
+      let cursor = event.target.result;
+      if (!cursor) {
+        next();
+        return;
+      }
+
+      let messageRecord = cursor.value;
+      if (messageRecord.type == "sms") {
+        cursor.continue();
+        return;
+      }
+
+      // We can always retrieve transaction id from
+      // |messageRecord.headers["x-mms-transaction-id"]|.
+      if (messageRecord.hasOwnProperty("transactionId")) {
+        delete messageRecord.transactionId;
+      }
+
+      // xpconnect gives "undefined" for an unassigned argument of an interface
+      // method.
+      if (messageRecord.envelopeIdIndex === "undefined") {
+        delete messageRecord.envelopeIdIndex;
+      }
+
+      // Convert some header fields that were originally decoded as BooleanValue
+      // to numeric enums.
+      for (let field of ["x-mms-cancel-status",
+                         "x-mms-sender-visibility",
+                         "x-mms-read-status"]) {
+        let value = messageRecord.headers[field];
+        if (value !== undefined) {
+          messageRecord.headers[field] = value ? 128 : 129;
+        }
+      }
+
+      // For all sent and received MMS messages, we have to add their
+      // |readStatus| and |readTimestamp| attributes in |deliveryInfo| array.
+      let readReportRequested =
+        messageRecord.headers["x-mms-read-report"] || false;
+      for (let element of messageRecord.deliveryInfo) {
+        element.readStatus = readReportRequested
+                           ? MMS.DOM_READ_STATUS_PENDING
+                           : MMS.DOM_READ_STATUS_NOT_APPLICABLE;
+        element.readTimestamp = 0;
+      }
+
+      cursor.update(messageRecord);
+      cursor.continue();
+    };
+  },
+
   matchParsedPhoneNumbers: function matchParsedPhoneNumbers(addr1, parsedAddr1,
                                                             addr2, parsedAddr2) {
     if ((parsedAddr1.internationalNumber &&
          parsedAddr1.internationalNumber === parsedAddr2.internationalNumber) ||
         (parsedAddr1.nationalNumber &&
          parsedAddr1.nationalNumber === parsedAddr2.nationalNumber)) {
       return true;
     }
@@ -1479,42 +1540,76 @@ MobileMessageDatabaseService.prototype =
           debug("findThreadRecordByParticipants: return "
                 + JSON.stringify(threadRecord));
         }
         aCallback(threadRecord, participantIds);
       };
     });
   },
 
+  newTxnWithCallback: function newTxnWithCallback(aCallback, aFunc, aStoreNames) {
+    let self = this;
+    this.newTxn(READ_WRITE, function(aError, aTransaction, aStores) {
+      let notifyResult = function(aRv, aMessageRecord) {
+        if (!aCallback) {
+          return;
+        }
+        let domMessage =
+          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
+        aCallback.notify(aRv, domMessage);
+      };
+
+      if (aError) {
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE, null);
+        return;
+      }
+
+      let capture = {};
+      aTransaction.oncomplete = function(event) {
+        notifyResult(Cr.NS_OK, capture.messageRecord);
+      };
+      aTransaction.onabort = function(event) {
+        // TODO bug 832140 check event.target.errorCode
+        notifyResult(Cr.NS_ERROR_FAILURE, null);
+      };
+
+      aFunc(capture, aStores);
+    }, aStoreNames);
+  },
+
   saveRecord: function saveRecord(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(rv) {
-        if (aCallback) {
-          aCallback.notify(rv, self.createDomMessageFromRecord(aMessageRecord));
+      let notifyResult = function(aRv, aMessageRecord) {
+        if (!aCallback) {
+          return;
         }
+        let domMessage =
+          aMessageRecord && self.createDomMessageFromRecord(aMessageRecord);
+        aCallback.notify(aRv, domMessage);
       };
 
       if (error) {
         // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE);
+        notifyResult(Cr.NS_ERROR_FAILURE, null);
         return;
       }
 
       txn.oncomplete = function oncomplete(event) {
         if (aMessageRecord.id > self.lastMessageId) {
           self.lastMessageId = aMessageRecord.id;
         }
-        notifyResult(Cr.NS_OK);
+        notifyResult(Cr.NS_OK, aMessageRecord);
       };
       txn.onabort = function onabort(event) {
         // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE);
+        notifyResult(Cr.NS_ERROR_FAILURE, null);
       };
 
       let messageStore = stores[0];
       let participantStore = stores[1];
       let threadStore = stores[2];
       self.replaceShortMessageOnSave(txn, messageStore, participantStore,
                                      threadStore, aMessageRecord, aAddresses);
     }, [MESSAGE_STORE_NAME, PARTICIPANT_STORE_NAME, THREAD_STORE_NAME]);
@@ -1729,51 +1824,29 @@ MobileMessageDatabaseService.prototype =
       debug("Setting message's delivery by " + type + " = "+ id
             + " receiver: " + receiver
             + " delivery: " + delivery
             + " deliveryStatus: " + deliveryStatus
             + " envelopeId: " + envelopeId);
     }
 
     let self = this;
-    let messageRecord;
-    function notifyResult(rv) {
-      if (!callback) {
-        return;
-      }
-      let domMessage = self.createDomMessageFromRecord(messageRecord);
-      callback.notify(rv, domMessage);
-    }
-
-    this.newTxn(READ_WRITE, function (error, txn, messageStore) {
-      if (error) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE);
-        return;
-      }
-      txn.oncomplete = function oncomplete(event) {
-        notifyResult(Cr.NS_OK);
-      };
-      txn.onabort = function onabort(event) {
-        // TODO bug 832140 check event.target.errorCode
-        notifyResult(Cr.NS_ERROR_FAILURE);
-      };
-
+    this.newTxnWithCallback(callback, function(aCapture, aMessageStore) {
       let getRequest;
       if (type === "messageId") {
-        getRequest = messageStore.get(id);
+        getRequest = aMessageStore.get(id);
       } else if (type === "envelopeId") {
-        getRequest = messageStore.index("envelopeId").get(id);
+        getRequest = aMessageStore.index("envelopeId").get(id);
       }
 
       getRequest.onsuccess = function onsuccess(event) {
-        messageRecord = event.target.result;
+        let messageRecord = event.target.result;
         if (!messageRecord) {
           if (DEBUG) debug("type = " + id + " is not found");
-          return;
+          throw Cr.NS_ERROR_FAILURE;
         }
 
         let isRecordUpdated = false;
 
         // Update |messageRecord.delivery| if needed.
         if (delivery && messageRecord.delivery != delivery) {
           messageRecord.delivery = delivery;
           messageRecord.deliveryIndex = [delivery, messageRecord.timestamp];
@@ -1818,28 +1891,29 @@ MobileMessageDatabaseService.prototype =
         // Update |messageRecord.envelopeIdIndex| if needed.
         if (envelopeId) {
           if (messageRecord.envelopeIdIndex != envelopeId) {
             messageRecord.envelopeIdIndex = envelopeId;
             isRecordUpdated = true;
           }
         }
 
+        aCapture.messageRecord = messageRecord;
         if (!isRecordUpdated) {
           if (DEBUG) {
             debug("The values of delivery, deliveryStatus and envelopeId " +
                   "don't need to be updated.");
           }
           return;
         }
 
         if (DEBUG) {
           debug("The delivery, deliveryStatus or envelopeId are updated.");
         }
-        messageStore.put(messageRecord);
+        aMessageStore.put(messageRecord);
       };
     });
   },
 
   fillReceivedMmsThreadParticipants: function fillReceivedMmsThreadParticipants(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
@@ -1935,65 +2009,75 @@ MobileMessageDatabaseService.prototype =
   },
 
   /**
    * nsIRilMobileMessageDatabaseService API
    */
 
   saveReceivedMessage: function saveReceivedMessage(aMessage, aCallback) {
     if ((aMessage.type != "sms" && aMessage.type != "mms") ||
-        (aMessage.type == "sms" && aMessage.messageClass == undefined) ||
+        (aMessage.type == "sms" && (aMessage.messageClass == undefined ||
+                                    aMessage.sender == undefined)) ||
         (aMessage.type == "mms" && (aMessage.delivery == undefined ||
-                                    aMessage.transactionId == undefined ||
-                                    !Array.isArray(aMessage.deliveryInfo) ||
+                                    aMessage.deliveryStatus == undefined ||
                                     !Array.isArray(aMessage.receivers))) ||
-        aMessage.sender == undefined ||
         aMessage.timestamp == undefined) {
       if (aCallback) {
         aCallback.notify(Cr.NS_ERROR_FAILURE, null);
       }
       return;
     }
-    let threadParticipants = [aMessage.sender];
+
+    let threadParticipants;
     if (aMessage.type == "mms") {
+      if (aMessage.headers.from) {
+        aMessage.sender = aMessage.headers.from.address;
+      } else {
+        aMessage.sender = "anonymous";
+      }
+
+      threadParticipants = [aMessage.sender];
       this.fillReceivedMmsThreadParticipants(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 (aMessage.type == "mms") {
-      aMessage.transactionIdIndex = aMessage.transactionId;
+      aMessage.transactionIdIndex = aMessage.headers["x-mms-transaction-id"];
       aMessage.isReadReportSent = false;
 
-      // If |deliveryTimestamp| is not specified, use 0 as default.
-      let deliveryInfo = aMessage.deliveryInfo;
-      for (let i = 0; i < deliveryInfo.length; i++) {
-        if (deliveryInfo[i].deliveryTimestamp == undefined) {
-          deliveryInfo[i].deliveryTimestamp = 0;
-        }
-      }
+      // 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;
       }
-
-      // If |deliveryTimestamp| is not specified, use 0 as default.
-      if (aMessage.deliveryTimestamp == undefined) {
-        aMessage.deliveryTimestamp = 0;
-      }
     }
     aMessage.deliveryIndex = [aMessage.delivery, timestamp];
 
     this.saveRecord(aMessage, threadParticipants, aCallback);
   },
 
   saveSendingMessage: function saveSendingMessage(aMessage, aCallback) {
     if ((aMessage.type != "sms" && aMessage.type != "mms") ||
@@ -2024,22 +2108,28 @@ MobileMessageDatabaseService.prototype =
         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 });
+          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];
@@ -2060,22 +2150,72 @@ MobileMessageDatabaseService.prototype =
   setMessageDeliveryByMessageId: function setMessageDeliveryByMessageId(
       messageId, receiver, delivery, deliveryStatus, envelopeId, callback) {
     this.updateMessageDeliveryById(messageId, "messageId",
                                    receiver, delivery, deliveryStatus,
                                    envelopeId, callback);
 
   },
 
-  setMessageDeliveryByEnvelopeId: function setMessageDeliveryByEnvelopeId(
-      envelopeId, receiver, delivery, deliveryStatus, callback) {
-    this.updateMessageDeliveryById(envelopeId, "envelopeId",
-                                   receiver, delivery, deliveryStatus,
-                                   null, callback);
-
+  setMessageDeliveryStatusByEnvelopeId:
+    function setMessageDeliveryStatusByEnvelopeId(aEnvelopeId, aReceiver,
+                                                  aDeliveryStatus, aCallback) {
+    this.updateMessageDeliveryById(aEnvelopeId, "envelopeId", aReceiver, null,
+                                   aDeliveryStatus, null, aCallback);
+  },
+
+  setMessageReadStatusByEnvelopeId:
+    function setMessageReadStatusByEnvelopeId(aEnvelopeId, aReceiver,
+                                              aReadStatus, aCallback) {
+    if (DEBUG) {
+      debug("Setting message's read status by envelopeId = " + aEnvelopeId +
+            ", receiver: " + aReceiver + ", readStatus: " + aReadStatus);
+    }
+
+    let self = this;
+    this.newTxnWithCallback(aCallback, function(aCapture, aMessageStore) {
+      let getRequest = aMessageStore.index("envelopeId").get(aEnvelopeId);
+      getRequest.onsuccess = function onsuccess(event) {
+        let messageRecord = event.target.result;
+        if (!messageRecord) {
+          if (DEBUG) debug("envelopeId '" + aEnvelopeId + "' not found");
+          throw Cr.NS_ERROR_FAILURE;
+        }
+
+        aCapture.messageRecord = messageRecord;
+
+        let isRecordUpdated = false;
+        self.forEachMatchedMmsDeliveryInfo(messageRecord.deliveryInfo,
+                                           aReceiver, function(aEntry) {
+          if (aEntry.readStatus == aReadStatus) {
+            return;
+          }
+
+          aEntry.readStatus = aReadStatus;
+          if (aReadStatus == MMS.DOM_READ_STATUS_SUCCESS) {
+            aEntry.readTimestamp = Date.now();
+          } else {
+            aEntry.readTimestamp = 0;
+          }
+          isRecordUpdated = true;
+        });
+
+        if (!isRecordUpdated) {
+          if (DEBUG) {
+            debug("The values of readStatus don't need to be updated.");
+          }
+          return;
+        }
+
+        if (DEBUG) {
+          debug("The readStatus is updated.");
+        }
+        aMessageStore.put(messageRecord);
+      };
+    });
   },
 
   getMessageRecordByTransactionId: function getMessageRecordByTransactionId(aTransactionId, aCallback) {
     if (DEBUG) debug("Retrieving message with transaction ID " + aTransactionId);
     let self = this;
     this.newTxn(READ_ONLY, function (error, txn, messageStore) {
       if (error) {
         if (DEBUG) debug(error);
--- a/dom/mobilemessage/src/gonk/mms_consts.js
+++ b/dom/mobilemessage/src/gonk/mms_consts.js
@@ -90,22 +90,42 @@ this.MMS_PDU_STATUS_EXPIRED       = 128;
 this.MMS_PDU_STATUS_RETRIEVED     = 129;
 this.MMS_PDU_STATUS_REJECTED      = 130;
 this.MMS_PDU_STATUS_DEFERRED      = 131;
 this.MMS_PDU_STATUS_UNRECOGNISED  = 132;
 this.MMS_PDU_STATUS_INDETERMINATE = 133;
 this.MMS_PDU_STATUS_FORWARDED     = 134;
 this.MMS_PDU_STATUS_UNREACHABLE   = 135;
 
+// X-Mms-Cancel-Status values
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.7
+this.MMS_PDU_CANCEL_STATUS_RECEIVED  = 128;
+this.MMS_PDU_CANCEL_STATUS_CORRUPTED = 129;
+
+// X-Mms-Sender-Visibility
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.52
+this.MMS_PDU_SENDER_VISIBILITY_HIDE = 128;
+this.MMS_PDU_SENDER_VISIBILITY_SHOW = 129;
+
+// X-Mms-Read-Status
+// @see OMA-TS-MMS_ENC-V1_3-20110913-A clause 7.3.38
+this.MMS_PDU_READ_STATUS_READ           = 128;
+this.MMS_PDU_READ_STATUS_DELETED_UNREAD = 129;
+
 // Maximum Values of MMS Parameters
 // @see OMA-TS-MMS_CONF-V1_3-20110511-C 10.2.5
 this.MMS_MAX_LENGTH_SUBJECT                = 40;
 this.MMS_MAX_LENGTH_RECIPIENT              = 312;
 this.MMS_MAX_TOTAL_RECIPIENTS              = 20;
 this.MMS_MAX_LENGTH_NAME_CONTENT_TYPE      = 40;
 this.MMS_MAX_LENGTH_MAILBOX_PORTION        = 256;
 
+this.DOM_READ_STATUS_NOT_APPLICABLE = "not-applicable";
+this.DOM_READ_STATUS_SUCCESS        = "success";
+this.DOM_READ_STATUS_PENDING        = "pending";
+this.DOM_READ_STATUS_ERROR          = "error";
+
 this.ALL_CONST_SYMBOLS = undefined; // We want ALL_CONST_SYMBOLS to be exported.
 this.ALL_CONST_SYMBOLS = Object.keys(this);
 
 // Allow this file to be imported via Components.utils.import().
 this.EXPORTED_SYMBOLS = ALL_CONST_SYMBOLS;
 
--- a/dom/mobilemessage/src/ipc/PSms.ipdl
+++ b/dom/mobilemessage/src/ipc/PSms.ipdl
@@ -113,16 +113,20 @@ child:
   NotifyFailedMessage(MobileMessageData aMessageData);
 
   NotifyDeliverySuccessMessage(MobileMessageData aMessageData);
 
   NotifyDeliveryErrorMessage(MobileMessageData aMessageData);
 
   NotifyReceivedSilentMessage(MobileMessageData aMessageData);
 
+  NotifyReadSuccessMessage(MobileMessageData aMessageData);
+
+  NotifyReadErrorMessage(MobileMessageData aMessageData);
+
 parent:
   /**
    * Sent when the child no longer needs to use sms.
    */
   __delete__();
 
   /**
    * Sent when the child makes an asynchronous request to the parent.
--- a/dom/mobilemessage/src/ipc/SmsChild.cpp
+++ b/dom/mobilemessage/src/ipc/SmsChild.cpp
@@ -113,16 +113,30 @@ SmsChild::RecvNotifyDeliveryErrorMessage
 
 bool
 SmsChild::RecvNotifyReceivedSilentMessage(const MobileMessageData& aData)
 {
   NotifyObserversWithMobileMessage(kSilentSmsReceivedObserverTopic, aData);
   return true;
 }
 
+bool
+SmsChild::RecvNotifyReadSuccessMessage(const MobileMessageData& aData)
+{
+  NotifyObserversWithMobileMessage(kSmsReadSuccessObserverTopic, aData);
+  return true;
+}
+
+bool
+SmsChild::RecvNotifyReadErrorMessage(const MobileMessageData& aData)
+{
+  NotifyObserversWithMobileMessage(kSmsReadErrorObserverTopic, aData);
+  return true;
+}
+
 PSmsRequestChild*
 SmsChild::AllocPSmsRequestChild(const IPCSmsRequest& aRequest)
 {
   MOZ_CRASH("Caller is supposed to manually construct a request!");
 }
 
 bool
 SmsChild::DeallocPSmsRequestChild(PSmsRequestChild* aActor)
--- a/dom/mobilemessage/src/ipc/SmsChild.h
+++ b/dom/mobilemessage/src/ipc/SmsChild.h
@@ -53,16 +53,22 @@ protected:
   RecvNotifyDeliverySuccessMessage(const MobileMessageData& aMessage) MOZ_OVERRIDE;
 
   virtual bool
   RecvNotifyDeliveryErrorMessage(const MobileMessageData& aMessage) MOZ_OVERRIDE;
 
   virtual bool
   RecvNotifyReceivedSilentMessage(const MobileMessageData& aMessage) MOZ_OVERRIDE;
 
+  virtual bool
+  RecvNotifyReadSuccessMessage(const MobileMessageData& aMessage) MOZ_OVERRIDE;
+
+  virtual bool
+  RecvNotifyReadErrorMessage(const MobileMessageData& aMessage) MOZ_OVERRIDE;
+
   virtual PSmsRequestChild*
   AllocPSmsRequestChild(const IPCSmsRequest& aRequest) MOZ_OVERRIDE;
 
   virtual bool
   DeallocPSmsRequestChild(PSmsRequestChild* aActor) MOZ_OVERRIDE;
 
   virtual PMobileMessageCursorChild*
   AllocPMobileMessageCursorChild(const IPCMobileMessageCursor& aCursor) MOZ_OVERRIDE;
--- a/dom/mobilemessage/src/ipc/SmsParent.cpp
+++ b/dom/mobilemessage/src/ipc/SmsParent.cpp
@@ -148,16 +148,18 @@ SmsParent::SmsParent()
   obs->AddObserver(this, kSmsReceivedObserverTopic, false);
   obs->AddObserver(this, kSmsRetrievingObserverTopic, false);
   obs->AddObserver(this, kSmsSendingObserverTopic, false);
   obs->AddObserver(this, kSmsSentObserverTopic, false);
   obs->AddObserver(this, kSmsFailedObserverTopic, false);
   obs->AddObserver(this, kSmsDeliverySuccessObserverTopic, false);
   obs->AddObserver(this, kSmsDeliveryErrorObserverTopic, false);
   obs->AddObserver(this, kSilentSmsReceivedObserverTopic, false);
+  obs->AddObserver(this, kSmsReadSuccessObserverTopic, false);
+  obs->AddObserver(this, kSmsReadErrorObserverTopic, false);
 }
 
 void
 SmsParent::ActorDestroy(ActorDestroyReason why)
 {
   nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
   if (!obs) {
     return;
@@ -166,16 +168,18 @@ SmsParent::ActorDestroy(ActorDestroyReas
   obs->RemoveObserver(this, kSmsReceivedObserverTopic);
   obs->RemoveObserver(this, kSmsRetrievingObserverTopic);
   obs->RemoveObserver(this, kSmsSendingObserverTopic);
   obs->RemoveObserver(this, kSmsSentObserverTopic);
   obs->RemoveObserver(this, kSmsFailedObserverTopic);
   obs->RemoveObserver(this, kSmsDeliverySuccessObserverTopic);
   obs->RemoveObserver(this, kSmsDeliveryErrorObserverTopic);
   obs->RemoveObserver(this, kSilentSmsReceivedObserverTopic);
+  obs->RemoveObserver(this, kSmsReadSuccessObserverTopic);
+  obs->RemoveObserver(this, kSmsReadErrorObserverTopic);
 }
 
 NS_IMETHODIMP
 SmsParent::Observe(nsISupports* aSubject, const char* aTopic,
                    const PRUnichar* aData)
 {
   if (!strcmp(aTopic, kSmsReceivedObserverTopic)) {
     MobileMessageData msgData;
@@ -267,16 +271,40 @@ SmsParent::Observe(nsISupports* aSubject
     }
 
     MobileMessageData msgData =
       static_cast<SmsMessage*>(smsMsg.get())->GetData();
     unused << SendNotifyReceivedSilentMessage(msgData);
     return NS_OK;
   }
 
+
+  if (!strcmp(aTopic, kSmsReadSuccessObserverTopic)) {
+    MobileMessageData msgData;
+    if (!GetMobileMessageDataFromMessage(aSubject, msgData)) {
+      NS_ERROR("Got a 'sms-read-success' topic without a valid message!");
+      return NS_OK;
+    }
+
+    unused << SendNotifyReadSuccessMessage(msgData);
+    return NS_OK;
+  }
+
+  if (!strcmp(aTopic, kSmsReadErrorObserverTopic)) {
+    MobileMessageData msgData;
+    if (!GetMobileMessageDataFromMessage(aSubject, msgData)) {
+      NS_ERROR("Got a 'sms-read-error' topic without a valid message!");
+      return NS_OK;
+    }
+
+    unused << SendNotifyReadErrorMessage(msgData);
+    return NS_OK;
+  }
+
+
   return NS_OK;
 }
 
 bool
 SmsParent::GetMobileMessageDataFromMessage(nsISupports *aMsg,
                                            MobileMessageData &aData)
 {
   nsCOMPtr<nsIDOMMozMmsMessage> mmsMsg = do_QueryInterface(aMsg);
--- a/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
+++ b/dom/mobilemessage/src/ipc/SmsTypes.ipdlh
@@ -4,16 +4,17 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 include protocol PBlob;
 
 using DeliveryState from "mozilla/dom/mobilemessage/Types.h";
 using DeliveryStatus from "mozilla/dom/mobilemessage/Types.h";
 using MessageClass from "mozilla/dom/mobilemessage/Types.h";
+using ReadStatus from "mozilla/dom/mobilemessage/Types.h";
 using ReadState from "mozilla/dom/mobilemessage/Types.h";
 using MessageType from "mozilla/dom/mobilemessage/Types.h";
 
 namespace mozilla {
 namespace dom {
 namespace mobilemessage {
 
 struct SmsSegmentInfoData
@@ -46,16 +47,18 @@ struct MmsAttachmentData
   PBlob content;
 };
 
 struct MmsDeliveryInfoData
 {
   nsString        receiver;
   DeliveryStatus  deliveryStatus;
   uint64_t        deliveryTimestamp;
+  ReadStatus      readStatus;
+  uint64_t        readTimestamp;
 };
 
 struct MmsMessageData
 {
   int32_t               id;
   uint64_t              threadId;
   nsString              iccId;
   DeliveryState         delivery;
--- a/dom/mobilemessage/tests/test_mms_pdu_helper.js
+++ b/dom/mobilemessage/tests/test_mms_pdu_helper.js
@@ -173,16 +173,48 @@ add_test(function test_MmsHeader_encode(
   wsp_encode_test(MMS.MmsHeader, {name: "X-Mms-Message-Type",
                                   value: MMS_PDU_TYPE_SEND_REQ},
                   [0x80 | 0x0C, MMS_PDU_TYPE_SEND_REQ]);
 
   run_next_test();
 });
 
 //
+// Test target: CancelStatusValue
+//
+
+//// CancelStatusValue.decode ////
+
+add_test(function test_CancelStatusValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 129)) {
+      wsp_decode_test(MMS.CancelStatusValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.CancelStatusValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// CancelStatusValue.encode ////
+
+add_test(function test_CancelStatusValue_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 129)) {
+      wsp_encode_test(MMS.CancelStatusValue, i, [i]);
+    } else {
+      wsp_encode_test(MMS.CancelStatusValue, i, null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
 // Test target: ContentClassValue
 //
 
 //// ContentClassValue.decode ////
 
 add_test(function test_ContentClassValue_decode() {
   for (let i = 0; i < 256; i++) {
     if ((i >= 128) && (i <= 135)) {
@@ -653,16 +685,48 @@ add_test(function test_PriorityValue_enc
       wsp_encode_test(MMS.PriorityValue, i, null, "CodeError");
     }
   }
 
   run_next_test();
 });
 
 //
+// Test target: ReadStatusValue
+//
+
+//// ReadStatusValue.decode ////
+
+add_test(function test_ReadStatusValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 129)) {
+      wsp_decode_test(MMS.ReadStatusValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.ReadStatusValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// ReadStatusValue.encode ////
+
+add_test(function test_ReadStatusValue_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 129)) {
+      wsp_encode_test(MMS.ReadStatusValue, i, [i]);
+    } else {
+      wsp_encode_test(MMS.ReadStatusValue, i, null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
 // Test target: RecommendedRetrievalModeValue
 //
 
 //// RecommendedRetrievalModeValue.decode ////
 
 add_test(function test_RecommendedRetrievalModeValue_decode() {
   for (let i = 0; i < 256; i++) {
     if (i == 128) {
@@ -761,16 +825,48 @@ add_test(function test_RetrieveStatusVal
                       MMS_PDU_ERROR_PERMANENT_FAILURE);
     }
   }
 
   run_next_test();
 });
 
 //
+// Test target: SenderVisibilityValue
+//
+
+//// SenderVisibilityValue.decode ////
+
+add_test(function test_SenderVisibilityValue_decode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 129)) {
+      wsp_decode_test(MMS.SenderVisibilityValue, [i], i);
+    } else {
+      wsp_decode_test(MMS.SenderVisibilityValue, [i], null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//// SenderVisibilityValue.encode ////
+
+add_test(function test_SenderVisibilityValue_encode() {
+  for (let i = 0; i < 256; i++) {
+    if ((i >= 128) && (i <= 129)) {
+      wsp_encode_test(MMS.SenderVisibilityValue, i, [i]);
+    } else {
+      wsp_encode_test(MMS.SenderVisibilityValue, i, null, "CodeError");
+    }
+  }
+
+  run_next_test();
+});
+
+//
 // Test target: StatusValue
 //
 
 //// StatusValue.decode ////
 
 add_test(function test_StatusValue_decode() {
   for (let i = 0; i < 256; i++) {
     if ((i >= 128) && (i <= 135)) {
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -193,25 +193,25 @@ var WifiManager = (function() {
   }
 
   function unloadDriver(callback) {
     if (!unloadDriverEnabled) {
       // Unloading drivers is generally unnecessary and
       // can trigger bugs in some drivers.
       // On properly written drivers, bringing the interface
       // down powers down the interface.
+      notify("supplicantlost", { success: true });
       callback(0);
-      notify("supplicantlost", { success: true });
       return;
     }
 
     wifiCommand.unloadDriver(function(status) {
       driverLoaded = (status < 0);
+      notify("supplicantlost", { success: true });
       callback(status);
-      notify("supplicantlost", { success: true });
     });
   }
 
   // A note about background scanning:
   // Normally, background scanning shouldn't be necessary as wpa_supplicant
   // has the capability to automatically schedule its own scans at appropriate
   // intervals. However, with some drivers, this appears to get stuck after
   // three scans, so we enable the driver's background scanning to work around
@@ -414,22 +414,16 @@ var WifiManager = (function() {
     if (handler) {
       if (!eventObject)
         eventObject = ({});
       handler.call(eventObject);
     }
   }
 
   function notifyStateChange(fields) {
-    // Don't handle any state change when and after disabling.
-    if (manager.state === "DISABLING" ||
-        manager.state === "UNINITIALIZED") {
-      return false;
-    }
-
     // If we're already in the COMPLETED state, we might receive events from
     // the supplicant that tell us that we're re-authenticating or reminding
     // us that we're associated to a network. In those cases, we don't need to
     // do anything, so just ignore them.
     if (manager.state === "COMPLETED" &&
         fields.state !== "DISCONNECTED" &&
         fields.state !== "INTERFACE_DISABLED" &&
         fields.state !== "INACTIVE" &&
@@ -441,22 +435,29 @@ var WifiManager = (function() {
     if (manager.backgroundScanEnabled &&
         (fields.state === "ASSOCIATING" ||
          fields.state === "ASSOCIATED" ||
          fields.state === "FOUR_WAY_HANDSHAKE" ||
          fields.state === "GROUP_HANDSHAKE" ||
          fields.state === "COMPLETED")) {
       setBackgroundScan("OFF", function() {});
     }
+
     fields.prevState = manager.state;
-    manager.state = fields.state;
-
     // Detect wpa_supplicant's loop iterations.
     manager.supplicantLoopDetection(fields.prevState, fields.state);
     notify("statechange", fields);
+
+    // Don't update state when and after disabling.
+    if (manager.state === "DISABLING" ||
+        manager.state === "UNINITIALIZED") {
+      return false;
+    }
+
+    manager.state = fields.state;
     return true;
   }
 
   function parseStatus(status) {
     if (status === null) {
       debug("Unable to get wpa supplicant's status");
       return;
     }
--- a/testing/mochitest/mach_commands.py
+++ b/testing/mochitest/mach_commands.py
@@ -94,17 +94,19 @@ class MochitestRunner(MozbuildObject):
         build_path = os.path.join(self.topobjdir, 'build')
         if build_path not in sys.path:
             sys.path.append(build_path)
 
         self.tests_dir = os.path.join(self.topobjdir, '_tests')
         self.mochitest_dir = os.path.join(self.tests_dir, 'testing', 'mochitest')
         self.lib_dir = os.path.join(self.topobjdir, 'dist', 'lib')
 
-    def run_b2g_test(self, test_file=None, b2g_home=None, xre_path=None, **kwargs):
+    def run_b2g_test(self, test_file=None, b2g_home=None, xre_path=None,
+                     total_chunks=None, this_chunk=None, no_window=None,
+                     **kwargs):
         """Runs a b2g mochitest.
 
         test_file is a path to a test file. It can be a relative path from the
         top source directory, an absolute filename, or a directory containing
         test files.
         """
         # Need to call relpath before os.chdir() below.
         test_path = ''
@@ -139,16 +141,19 @@ class MochitestRunner(MozbuildObject):
             options.testPath = test_path
         elif conditions.is_b2g_desktop:
             options.testManifest = 'b2g-desktop.json'
         else:
             options.testManifest = 'b2g.json'
 
         for k, v in kwargs.iteritems():
             setattr(options, k, v)
+        options.noWindow = no_window
+        options.totalChunks = total_chunks
+        options.thisChunk = this_chunk
 
         options.consoleLevel = 'INFO'
         if conditions.is_b2g_desktop(self):
             if self.substs.get('ENABLE_MARIONETTE') != '1':
                 print(MARIONETTE_DISABLED % ('mochitest-b2g-desktop',
                                              self.mozconfig['path']))
                 return 1