Merge b2g-inbound to m-c.
authorRyan VanderMeulen <ryanvm@gmail.com>
Wed, 20 Nov 2013 20:42:57 -0500
changeset 156520 cf378dddfac8
parent 156466 2850ab9720c9 (current diff)
parent 156519 8acf57418d59 (diff)
child 156733 7427eede548f
push id25683
push userryanvm@gmail.com
push date2013-11-21 01:42 +0000
treeherdermozilla-central@cf378dddfac8 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone28.0a1
first release with
nightly linux32
cf378dddfac8 / 28.0a1 / 20131121030201 / files
nightly linux64
cf378dddfac8 / 28.0a1 / 20131121030201 / files
nightly mac
cf378dddfac8 / 28.0a1 / 20131121030201 / files
nightly win32
cf378dddfac8 / 28.0a1 / 20131121030201 / files
nightly win64
cf378dddfac8 / 28.0a1 / 20131121030201 / files
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge b2g-inbound to m-c.
dom/bluetooth/BluetoothA2dpManager.cpp
dom/bluetooth/BluetoothA2dpManager.h
--- a/b2g/config/gaia.json
+++ b/b2g/config/gaia.json
@@ -1,4 +1,4 @@
 {
-    "revision": "14a570c0af0ad29420a47318576fc365ebf7b10a", 
+    "revision": "7c0504882446706ff1c1baa09607cfb91616a3c6", 
     "repo_path": "/integration/gaia-central"
 }
--- a/content/base/src/nsGkAtomList.h
+++ b/content/base/src/nsGkAtomList.h
@@ -714,17 +714,19 @@ GK_ATOM(onfrequencychange, "onfrequencyc
 GK_ATOM(onget, "onget")
 GK_ATOM(ongroupchange, "ongroupchange")
 GK_ATOM(onhashchange, "onhashchange")
 GK_ATOM(onheadphoneschange, "onheadphoneschange")
 GK_ATOM(onheld, "onheld")
 GK_ATOM(onhfpstatuschanged, "onhfpstatuschanged")
 GK_ATOM(onholding, "onholding")
 GK_ATOM(oniccchange, "oniccchange")
+GK_ATOM(oniccdetected, "oniccdetected")
 GK_ATOM(oniccinfochange, "oniccinfochange")
+GK_ATOM(oniccundetected, "oniccundetected")
 GK_ATOM(onincoming, "onincoming")
 GK_ATOM(oninput, "oninput")
 GK_ATOM(oninvalid, "oninvalid")
 GK_ATOM(onkeydown, "onkeydown")
 GK_ATOM(onkeypress, "onkeypress")
 GK_ATOM(onkeyup, "onkeyup")
 GK_ATOM(onlevelchange, "onlevelchange")
 GK_ATOM(onLoad, "onLoad")
--- a/content/events/test/test_all_synthetic_events.html
+++ b/content/events/test/test_all_synthetic_events.html
@@ -166,16 +166,20 @@ const kEventConstructors = {
   GamepadButtonEvent:                        { create: function (aName, aProps) {
                                                          return new GamepadButtonEvent(aName, aProps);
                                                        },
                                              },
   HashChangeEvent:                           { create: function (aName, aProps) {
                                                          return new HashChangeEvent(aName, aProps);
                                                        },
                                              },
+  IccChangeEvent:                            { create: function (aName, aProps) {
+                                                         return new IccChangeEvent(aName, aProps);
+                                                       },
+                                             },
   IDBVersionChangeEvent:                     { create: function (aName, aProps) {
                                                          return new IDBVersionChangeEvent(aName, aProps);
                                                        },
                                              },
   KeyEvent:                                  { create: function (aName, aProps) {
                                                          var e = document.createEvent("keyboardevent");
                                                          e.initKeyEvent(aName, aProps.bubbles, aProps.cancelable,
                                                                         aProps.view,
--- a/content/html/content/public/HTMLMediaElement.h
+++ b/content/html/content/public/HTMLMediaElement.h
@@ -364,16 +364,20 @@ public:
     SetHTMLAttr(nsGkAtoms::crossorigin, aValue, aRv);
   }
 
   uint16_t NetworkState() const
   {
     return mNetworkState;
   }
 
+  // Called by the media decoder object, on the main thread,
+  // when the connection between Rtsp server and client gets lost.
+  void ResetConnectionState() MOZ_FINAL MOZ_OVERRIDE;
+
   // XPCOM GetPreload() is OK
   void SetPreload(const nsAString& aValue, ErrorResult& aRv)
   {
     SetHTMLAttr(nsGkAtoms::preload, aValue, aRv);
   }
 
   already_AddRefed<TimeRanges> Buffered() const;
 
--- a/content/html/content/src/HTMLMediaElement.cpp
+++ b/content/html/content/src/HTMLMediaElement.cpp
@@ -2064,16 +2064,29 @@ void HTMLMediaElement::SetPlayedOrSeeked
     return;
   }
   frame->PresContext()->PresShell()->FrameNeedsReflow(frame,
                                                       nsIPresShell::eTreeChange,
                                                       NS_FRAME_IS_DIRTY);
 }
 
 void
+HTMLMediaElement::ResetConnectionState()
+{
+  mBegun = false;
+  SetCurrentTime(0);
+  FireTimeUpdate(false);
+  DispatchAsyncEvent(NS_LITERAL_STRING("ended"));
+  mNetworkState = nsIDOMHTMLMediaElement::NETWORK_EMPTY;
+  AddRemoveSelfReference();
+  ChangeDelayLoadStatus(false);
+  ChangeReadyState(nsIDOMHTMLMediaElement::HAVE_NOTHING);
+}
+
+void
 HTMLMediaElement::Play(ErrorResult& aRv)
 {
   StopSuspendingAfterFirstFrame();
   SetPlayedOrSeeked(true);
 
   if (mNetworkState == nsIDOMHTMLMediaElement::NETWORK_EMPTY) {
     aRv = Load();
     if (aRv.Failed()) {
--- a/content/media/MediaDecoder.cpp
+++ b/content/media/MediaDecoder.cpp
@@ -809,16 +809,33 @@ void MediaDecoder::ResourceLoaded()
   }
 
   // Ensure the final progress event gets fired
   if (mOwner) {
     mOwner->ResourceLoaded();
   }
 }
 
+void MediaDecoder::ResetConnectionState()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  if (mShuttingDown)
+    return;
+
+  if (mOwner) {
+    // Notify the media element that connection gets lost.
+    mOwner->ResetConnectionState();
+  }
+
+  // Since we have notified the media element the connection
+  // lost event, the decoder will be reloaded when user tries
+  // to play the Rtsp streaming next time.
+  Shutdown();
+}
+
 void MediaDecoder::NetworkError()
 {
   MOZ_ASSERT(NS_IsMainThread());
   if (mShuttingDown)
     return;
 
   if (mOwner)
     mOwner->NetworkError();
--- a/content/media/MediaDecoder.h
+++ b/content/media/MediaDecoder.h
@@ -242,16 +242,19 @@ public:
     PLAY_STATE_SEEKING,
     PLAY_STATE_ENDED,
     PLAY_STATE_SHUTDOWN
   };
 
   MediaDecoder();
   virtual ~MediaDecoder();
 
+  // Reset the decoder and notify the media element that
+  // server connection is closed.
+  virtual void ResetConnectionState();
   // Create a new decoder of the same type as this one.
   // Subclasses must implement this.
   virtual MediaDecoder* Clone() = 0;
   // Create a new state machine to run this decoder.
   // Subclasses must implement this.
   virtual MediaDecoderStateMachine* CreateStateMachine() = 0;
 
   // Call on the main thread only.
--- a/content/media/MediaDecoderOwner.h
+++ b/content/media/MediaDecoderOwner.h
@@ -134,14 +134,18 @@ public:
   // the data for the next frame is available. This method will
   // decide whether to set the ready state to HAVE_CURRENT_DATA,
   // HAVE_FUTURE_DATA or HAVE_ENOUGH_DATA.
   virtual void UpdateReadyStateForData(NextFrameStatus aNextFrame) = 0;
 
   // Called by the media decoder and the video frame to get the
   // ImageContainer containing the video data.
   virtual VideoFrameContainer* GetVideoFrameContainer() = 0;
+
+  // Called by the media decoder object, on the main thread,
+  // when the connection between Rtsp server and client gets lost.
+  virtual void ResetConnectionState() = 0;
 };
 
 }
 
 #endif
 
--- 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/RtspMediaResource.cpp
+++ b/content/media/RtspMediaResource.cpp
@@ -67,16 +67,17 @@ public:
   };
   ~RtspTrackBuffer() {
     MOZ_COUNT_DTOR(RtspTrackBuffer);
     mRingBuffer = nullptr;
   };
   void Start() {
     MonitorAutoLock monitor(mMonitor);
     mIsStarted = true;
+    mFrameType = 0;
   }
   void Stop() {
     MonitorAutoLock monitor(mMonitor);
     mIsStarted = false;
   }
 
   // Read the data from mRingBuffer[mConsumerIdx*mSlotSize] into aToBuffer.
   // If the aToBufferSize is smaller than mBufferSlotDataLength[mConsumerIdx],
@@ -439,16 +440,19 @@ RtspMediaResource::OnMediaDataAvailable(
   return NS_OK;
 }
 
 nsresult
 RtspMediaResource::OnConnected(uint8_t aTrackIdx,
                                nsIStreamingProtocolMetaData *meta)
 {
   if (mIsConnected) {
+    for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
+      mTrackBuffer[i]->Start();
+    }
     return NS_OK;
   }
 
   uint8_t tracks;
   mMediaStreamController->GetTotalTracks(&tracks);
   uint64_t duration = 0;
   for (int i = 0; i < tracks; ++i) {
     nsCString rtspTrackId("RtspTrack");
@@ -518,19 +522,29 @@ RtspMediaResource::OnDisconnected(uint8_
 {
   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
 
   for (uint32_t i = 0 ; i < mTrackBuffer.Length(); ++i) {
     mTrackBuffer[i]->Stop();
     mTrackBuffer[i]->Reset();
   }
 
-  if (aReason == NS_ERROR_CONNECTION_REFUSED) {
+  if (aReason == NS_ERROR_NOT_INITIALIZED ||
+      aReason == NS_ERROR_CONNECTION_REFUSED ||
+      aReason == NS_ERROR_NOT_CONNECTED) {
+
+    RTSPMLOG("Error in OnDisconnected 0x%x", aReason);
+
     mDecoder->NetworkError();
+    return NS_OK;
   }
+
+  // Resetting the decoder and media element when the connection
+  // between Rtsp client and server goes down.
+  mDecoder->ResetConnectionState();
   return NS_OK;
 }
 
 void RtspMediaResource::Suspend(bool aCloseImmediately)
 {
   NS_ASSERTION(NS_IsMainThread(), "Don't call on non-main thread");
 
   MediaDecoderOwner* owner = mDecoder->GetMediaOwner();
--- 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
@@ -127,28 +127,27 @@ OpusTrackEncoder::~OpusTrackEncoder()
   if (mEncoder) {
     opus_encoder_destroy(mEncoder);
   }
 }
 
 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,
--- 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/omx/RtspOmxReader.cpp
+++ b/content/media/omx/RtspOmxReader.cpp
@@ -119,17 +119,26 @@ status_t RtspMediaSource::read(MediaBuff
 
   while (1) {
     err = mGroup->acquire_buffer(&mBuffer);
     NS_ENSURE_TRUE(err == OK, err);
 
     rv = mRtspResource->ReadFrameFromTrack((uint8_t *)mBuffer->data(),
                                            mFrameMaxSize, mTrackIdx, readCount,
                                            time, actualFrameSize);
-    NS_ENSURE_SUCCESS(rv, ERROR_IO);
+    if (NS_FAILED(rv)) {
+      // Release mGroup and mBuffer.
+      stop();
+      // Since RtspMediaSource is an implementation of Android media source,
+      // it's held by OMXCodec and isn't released yet. So we have to re-construct
+      // mGroup and mBuffer.
+      start();
+      NS_WARNING("ReadFrameFromTrack failed; releasing buffers and returning.");
+      return ERROR_CONNECTION_LOST;
+    }
     if (actualFrameSize > mFrameMaxSize) {
       // release mGroup and mBuffer
       stop();
       // re-construct mGroup and mBuffer
       mFrameMaxSize = actualFrameSize;
       err = start();
       NS_ENSURE_TRUE(err == OK, err);
     } else {
--- 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/alarm/AlarmsManager.js
+++ b/dom/alarm/AlarmsManager.js
@@ -32,17 +32,18 @@ function AlarmsManager()
 AlarmsManager.prototype = {
 
   __proto__: DOMRequestIpcHelper.prototype,
 
   classID : ALARMSMANAGER_CID,
 
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozAlarmsManager,
                                           Ci.nsIDOMGlobalPropertyInitializer,
-                                          Ci.nsISupportsWeakReference]),
+                                          Ci.nsISupportsWeakReference,
+                                          Ci.nsIObserver]),
 
   classInfo : XPCOMUtils.generateCI({ classID: ALARMSMANAGER_CID,
                                       contractID: ALARMSMANAGER_CONTRACTID,
                                       classDescription: "AlarmsManager",
                                       interfaces: [nsIDOMMozAlarmsManager],
                                       flags: nsIClassInfo.DOM_OBJECT }),
 
   add: function add(aDate, aRespectTimezone, aData) {
--- a/dom/apps/src/InterAppConnection.js
+++ b/dom/apps/src/InterAppConnection.js
@@ -39,17 +39,18 @@ InterAppConnection.prototype = {
 
   classDescription: "MozInterAppConnection",
 
   classID: Components.ID("{9dbfa904-0718-11e3-8e77-0721a45514b8}"),
 
   contractID: "@mozilla.org/dom/inter-app-connection;1",
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
   __init: function(aKeyword, aPublisher, aSubscriber) {
     if (DEBUG) {
       debug("__init: aKeyword: " + aKeyword +
             " aPublisher: " + aPublisher + " aSubscriber: " + aSubscriber);
     }
     this.keyword = aKeyword;
     this.publisher = aPublisher;
--- a/dom/apps/src/InterAppMessagePort.js
+++ b/dom/apps/src/InterAppMessagePort.js
@@ -42,17 +42,18 @@ InterAppMessagePort.prototype = {
 
   classDescription: "MozInterAppMessagePort",
 
   classID: Components.ID("{c66e0f8c-e3cb-11e2-9e85-43ef6244b884}"),
 
   contractID: "@mozilla.org/dom/inter-app-message-port;1",
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
   // Ci.nsIDOMGlobalPropertyInitializer implementation.
   init: function(aWindow) {
     if (DEBUG) debug("Calling init().");
 
     this.initDOMRequestHelper(aWindow, kMessages);
 
     let principal = aWindow.document.nodePrincipal;
--- a/dom/apps/src/Webapps.js
+++ b/dom/apps/src/Webapps.js
@@ -243,16 +243,17 @@ WebappsRegistry.prototype = {
     // Only pages with the webapps-manage permission set can get access to
     // the mgmt object.
     this.hasMgmtPrivilege = perm == Ci.nsIPermissionManager.ALLOW_ACTION;
   },
 
   classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver,
                                          Ci.mozIDOMApplicationRegistry,
                                          Ci.mozIDOMApplicationRegistry2,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
 
   classInfo: XPCOMUtils.generateCI({classID: Components.ID("{fff440b3-fae2-45c1-bf03-3b5a2e432270}"),
                                     contractID: "@mozilla.org/webapps;1",
                                     interfaces: [Ci.mozIDOMApplicationRegistry,
                                                  Ci.mozIDOMApplicationRegistry2],
@@ -629,17 +630,18 @@ WebappsApplication.prototype = {
         req.resolve(connections);
         break;
     }
   },
 
   classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplication,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
   classInfo: XPCOMUtils.generateCI({classID: Components.ID("{723ed303-7757-4fb0-b261-4f78b1f6bd22}"),
                                     contractID: "@mozilla.org/webapps/application;1",
                                     interfaces: [Ci.mozIDOMApplication],
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Application"})
 }
 
@@ -784,17 +786,19 @@ WebappsApplicationMgmt.prototype = {
     }
     if (aMessage.name !== "Webapps:Uninstall:Broadcast:Return:OK") {
       this.removeRequest(msg.requestID);
     }
   },
 
   classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
 
-  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt, Ci.nsISupportsWeakReference]),
+  QueryInterface: XPCOMUtils.generateQI([Ci.mozIDOMApplicationMgmt,
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
   classInfo: XPCOMUtils.generateCI({classID: Components.ID("{8c1bca96-266f-493a-8d57-ec7a95098c15}"),
                                     contractID: "@mozilla.org/webapps/application-mgmt;1",
                                     interfaces: [Ci.mozIDOMApplicationMgmt],
                                     flags: Ci.nsIClassInfo.DOM_OBJECT,
                                     classDescription: "Webapps Application Mgmt"})
 }
 
--- a/dom/base/DOMRequestHelper.jsm
+++ b/dom/base/DOMRequestHelper.jsm
@@ -136,33 +136,39 @@ DOMRequestIpcHelper.prototype = {
    *    }
    *    where 'name' is the message identifier and 'strongRef' a boolean
    *    indicating if the listener should be a strong referred one or not.
    *
    *  - or only strings containing the message name, in which case the listener
    *    will be added as a weak referred one by default.
    */
   initDOMRequestHelper: function(aWindow, aMessages) {
+    // Query our required interfaces to force a fast fail if they are not
+    // provided. These calls will throw if the interface is not available.
+    this.QueryInterface(Ci.nsISupportsWeakReference);
+    this.QueryInterface(Ci.nsIObserver);
+
     if (aMessages) {
       this.addMessageListeners(aMessages);
     }
 
     this._id = this._getRandomId();
 
     this._window = aWindow;
     if (this._window) {
       // We don't use this.innerWindowID, but other classes rely on it.
       let util = this._window.QueryInterface(Ci.nsIInterfaceRequestor)
                              .getInterface(Ci.nsIDOMWindowUtils);
       this.innerWindowID = util.currentInnerWindowID;
     }
 
     this._destroyed = false;
 
-    Services.obs.addObserver(this, "inner-window-destroyed", false);
+    Services.obs.addObserver(this, "inner-window-destroyed",
+                             /* weak-ref */ true);
   },
 
   destroyDOMRequestHelper: function() {
     if (this._destroyed) {
       return;
     }
 
     this._destroyed = true;
--- a/dom/base/Navigator.cpp
+++ b/dom/base/Navigator.cpp
@@ -1231,18 +1231,17 @@ Navigator::GetMozIccManager(ErrorResult&
 {
   if (!mIccManager) {
     if (!mWindow) {
       aRv.Throw(NS_ERROR_UNEXPECTED);
       return nullptr;
     }
     NS_ENSURE_TRUE(mWindow->GetDocShell(), nullptr);
 
-    mIccManager = new IccManager();
-    mIccManager->Init(mWindow);
+    mIccManager = new IccManager(mWindow);
   }
 
   return mIccManager;
 }
 
 #endif // MOZ_B2G_RIL
 
 #ifdef MOZ_GAMEPAD
--- a/dom/base/test/test_domrequesthelper.xul
+++ b/dom/base/test/test_domrequesthelper.xul
@@ -11,22 +11,34 @@
         onload="start();">
   <title>DOMRequestHelper Test</title>
 
   <script type="application/javascript"
           src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"/>
 
   <script type="application/javascript">
   <![CDATA[
-    Components.utils.import("resource://gre/modules/DOMRequestHelper.jsm");
+    const Cc = Components.classes;
+    const Cu = Components.utils;
+    const Ci = Components.interfaces;
+    Cu.import("resource://gre/modules/DOMRequestHelper.jsm");
+    let obs = Cc["@mozilla.org/observer-service;1"].
+              getService(Ci.nsIObserverService);
 
     function DummyHelperSubclass() {
+      this.onuninit = null;
     }
     DummyHelperSubclass.prototype = {
-      __proto__: DOMRequestIpcHelper.prototype
+      __proto__: DOMRequestIpcHelper.prototype,
+      uninit: function() {
+        if (typeof this.onuninit === "function") {
+          this.onuninit();
+        }
+        this.onuninit = null;
+      }
     };
 
     var dummy = new DummyHelperSubclass();
 
     /**
      * Init & destroy.
      */
     function initDOMRequestHelperTest(aMessages) {
@@ -73,16 +85,88 @@
     }
 
     function removeMessageListenersTest(aMessages, aExpectedListeners, aCount) {
       dummy.removeMessageListeners(aMessages);
       checkMessageListeners(aExpectedListeners, aCount);
     }
 
     /**
+     * Utility function to test window destruction behavior.  In general this
+     * function does the following:
+     *
+     *  1) Create a new iframe
+     *  2) Create a new DOMRequestHelper
+     *  3) initDOMRequestHelper(), optionally with weak or strong listeners
+     *  4) Optionally force a garbage collection to reap weak references
+     *  5) Destroy the iframe triggering an inner-window-destroyed event
+     *  6) Callback with a boolean indicating if DOMRequestHelper.uninit() was
+     *     called.
+     *
+     * Example usage:
+     *
+     *    checkWindowDestruction({ messages: ["foo"], gc: true },
+     *                           function(uninitCalled) {
+     *      // expect uninitCalled === false since GC with only weak refs
+     *    });
+     */
+    const TOPIC = "inner-window-destroyed";
+    function checkWindowDestruction(aOptions, aCallback) {
+      aOptions = aOptions || {};
+      aOptions.messages = aOptions.messages || [];
+      aOptions.gc = !!aOptions.gc;
+
+      if (typeof aCallback !== "function") {
+        aCallback = function() { };
+      }
+
+      let uninitCalled = false;
+
+      // Use a secondary observer so we know when to expect the uninit().  We
+      // can then reasonably expect uninitCalled to be set properly on the
+      // next tick.
+      let observer = {
+        observe: function(aSubject, aTopic, aData) {
+          if (aTopic !== TOPIC) {
+            return;
+          }
+          obs.removeObserver(observer, TOPIC);
+          setTimeout(function() {
+            aCallback(uninitCalled);
+          });
+        }
+      };
+
+      let frame = document.createElement("iframe");
+      frame.onload = function() {
+        obs.addObserver(observer, TOPIC, false);
+        // Create dummy DOMRequestHelper specific to checkWindowDestruction()
+        let cwdDummy = new DummyHelperSubclass();
+        cwdDummy.onuninit = function() {
+          uninitCalled = true;
+        };
+        cwdDummy.initDOMRequestHelper(frame.contentWindow, aOptions.messages);
+        // Make sure to clear our strong ref here so that we can test our
+        // weak reference listeners and observer.
+        cwdDummy = null;
+        if (aOptions.gc) {
+          Cu.schedulePreciseGC(function() {
+            SpecialPowers.DOMWindowUtils.cycleCollect();
+            SpecialPowers.DOMWindowUtils.garbageCollect();
+            SpecialPowers.DOMWindowUtils.garbageCollect();
+            document.documentElement.removeChild(frame);
+          });
+          return;
+        }
+        document.documentElement.removeChild(frame);
+      };
+      document.documentElement.appendChild(frame);
+    }
+
+    /**
      * Test steps.
      */
     var tests = [
       function() {
         ok(true, "== InitDOMRequestHelper no messages");
         initDOMRequestHelperTest(null);
         next();
       },
@@ -329,16 +413,62 @@
         }).then(function(unused) {
           var r = dummy.takePromiseResolver(id);
           ok(resolver === r, "take should succeed");
 
           r = dummy.getPromiseResolver(id);
           ok(r === undefined, "takeResolver: get failed");
           next();
         });
+      },
+      function() {
+        ok(true, "== Test window destroyed without messages and without GC");
+        checkWindowDestruction({ gc: false }, function(uninitCalled) {
+          ok(uninitCalled, "uninit() should have been called");
+          next();
+        });
+      },
+      function() {
+        ok(true, "== Test window destroyed without messages and with GC");
+        checkWindowDestruction({ gc: true }, function(uninitCalled) {
+          ok(!uninitCalled, "uninit() should NOT have been called");
+          next();
+        });
+      },
+      function() {
+        ok(true, "== Test window destroyed with weak messages and without GC");
+        checkWindowDestruction({ messages: [{ name: "foo", strongRef: false }],
+                                 gc: false }, function(uninitCalled) {
+          ok(uninitCalled, "uninit() should have been called");
+          next();
+        });
+      },
+      function() {
+        ok(true, "== Test window destroyed with weak messages and with GC");
+        checkWindowDestruction({ messages: [{ name: "foo", strongRef: false }],
+                                 gc: true }, function(uninitCalled) {
+          ok(!uninitCalled, "uninit() should NOT have been called");
+          next();
+        });
+      },
+      function() {
+        ok(true, "== Test window destroyed with strong messages and without GC");
+        checkWindowDestruction({ messages: [{ name: "foo", strongRef: true }],
+                                 gc: false }, function(uninitCalled) {
+          ok(uninitCalled, "uninit() should have been called");
+          next();
+        });
+      },
+      function() {
+        ok(true, "== Test window destroyed with strong messages and with GC");
+        checkWindowDestruction({ messages: [{ name: "foo", strongRef: true }],
+                                 gc: true }, function(uninitCalled) {
+          ok(uninitCalled, "uninit() should have been called");
+          next();
+        });
       }
     ];
 
     function next() {
       if (!tests.length) {
         SimpleTest.finish();
         return;
       }
--- a/dom/bindings/Bindings.conf
+++ b/dom/bindings/Bindings.conf
@@ -784,16 +784,20 @@ DOMInterfaces = {
 {
     'workers': True,
 }],
 
 'MozCellBroadcast': {
     'nativeType': 'mozilla::dom::CellBroadcast',
 },
 
+'MozIcc': {
+    'nativeType': 'mozilla::dom::Icc',
+},
+
 'MozMobileConnectionArray': {
     'nativeType': 'mozilla::dom::network::MobileConnectionArray',
     'resultNotAddRefed': [ 'item' ]
 },
 
 'MozNamedAttrMap': {
     'nativeType': 'nsDOMAttributeMap',
 },
@@ -1850,17 +1854,18 @@ addExternalIface('imgIRequest', nativeTy
 addExternalIface('LockedFile')
 addExternalIface('MediaQueryList')
 addExternalIface('MenuBuilder', nativeType='nsIMenuBuilder', notflattened=True)
 addExternalIface('MozBoxObject', nativeType='nsIBoxObject')
 addExternalIface('MozConnection', headerFile='nsIDOMConnection.h')
 addExternalIface('MozControllers', nativeType='nsIControllers')
 addExternalIface('MozFrameLoader', nativeType='nsIFrameLoader', notflattened=True)
 addExternalIface('MozFrameRequestCallback', nativeType='nsIFrameRequestCallback',
-                 notflattened=True);
+                 notflattened=True)
+addExternalIface('MozIccInfo', headerFile='nsIDOMIccInfo.h')
 addExternalIface('MozIccManager', headerFile='nsIDOMIccManager.h')
 addExternalIface('MozMobileConnection', headerFile='nsIDOMMobileConnection.h')
 addExternalIface('MozMobileMessageManager', headerFile='nsIDOMMobileMessageManager.h')
 addExternalIface('MozObserver', nativeType='nsIObserver', notflattened=True)
 addExternalIface('MozRDFCompositeDataSource', nativeType='nsIRDFCompositeDataSource',
                  notflattened=True)
 addExternalIface('MozRDFResource', nativeType='nsIRDFResource', notflattened=True)
 addExternalIface('MozTreeBoxObject', nativeType='nsITreeBoxObject',
deleted file mode 100644
--- a/dom/bluetooth/BluetoothA2dpManager.cpp
+++ /dev/null
@@ -1,452 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#include "base/basictypes.h"
-
-#include "BluetoothA2dpManager.h"
-
-#include "BluetoothCommon.h"
-#include "BluetoothService.h"
-#include "BluetoothSocket.h"
-#include "BluetoothUtils.h"
-
-#include "mozilla/dom/bluetooth/BluetoothTypes.h"
-#include "mozilla/Services.h"
-#include "mozilla/StaticPtr.h"
-#include "nsIObserverService.h"
-#include "MainThreadUtils.h"
-
-
-using namespace mozilla;
-USING_BLUETOOTH_NAMESPACE
-
-namespace {
-  StaticRefPtr<BluetoothA2dpManager> sBluetoothA2dpManager;
-  bool sInShutdown = false;
-} // anonymous namespace
-
-NS_IMETHODIMP
-BluetoothA2dpManager::Observe(nsISupports* aSubject,
-                              const char* aTopic,
-                              const PRUnichar* aData)
-{
-  MOZ_ASSERT(sBluetoothA2dpManager);
-
-  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
-    HandleShutdown();
-    return NS_OK;
-  }
-
-  MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!");
-  return NS_ERROR_UNEXPECTED;
-}
-
-BluetoothA2dpManager::BluetoothA2dpManager()
-{
-  ResetA2dp();
-  ResetAvrcp();
-}
-
-bool
-BluetoothA2dpManager::Init()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  NS_ENSURE_TRUE(obs, false);
-  if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
-    BT_WARNING("Failed to add shutdown observer!");
-    return false;
-  }
-
-  return true;
-}
-
-BluetoothA2dpManager::~BluetoothA2dpManager()
-{
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  NS_ENSURE_TRUE_VOID(obs);
-  if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
-    BT_WARNING("Failed to remove shutdown observer!");
-  }
-}
-
-void
-BluetoothA2dpManager::ResetA2dp()
-{
-  mA2dpConnected = false;
-  mSinkState = SinkState::SINK_DISCONNECTED;
-  mController = nullptr;
-}
-
-void
-BluetoothA2dpManager::ResetAvrcp()
-{
-  mAvrcpConnected = false;
-  mDuration = 0;
-  mMediaNumber = 0;
-  mTotalMediaCount = 0;
-  mPosition = 0;
-  mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
-}
-
-static BluetoothA2dpManager::SinkState
-StatusStringToSinkState(const nsAString& aStatus)
-{
-  BluetoothA2dpManager::SinkState state =
-    BluetoothA2dpManager::SinkState::SINK_UNKNOWN;
-  if (aStatus.EqualsLiteral("disconnected")) {
-    state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED;
-  } else if (aStatus.EqualsLiteral("connecting")) {
-    state = BluetoothA2dpManager::SinkState::SINK_CONNECTING;
-  } else if (aStatus.EqualsLiteral("connected")) {
-    state = BluetoothA2dpManager::SinkState::SINK_CONNECTED;
-  } else if (aStatus.EqualsLiteral("playing")) {
-    state = BluetoothA2dpManager::SinkState::SINK_PLAYING;
-  } else {
-    BT_WARNING("Unknown sink state");
-  }
-  return state;
-}
-
-//static
-BluetoothA2dpManager*
-BluetoothA2dpManager::Get()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // If sBluetoothA2dpManager already exists, exit early
-  if (sBluetoothA2dpManager) {
-    return sBluetoothA2dpManager;
-  }
-
-  // If we're in shutdown, don't create a new instance
-  NS_ENSURE_FALSE(sInShutdown, nullptr);
-
-  // Create a new instance, register, and return
-  BluetoothA2dpManager* manager = new BluetoothA2dpManager();
-  NS_ENSURE_TRUE(manager->Init(), nullptr);
-
-  sBluetoothA2dpManager = manager;
-  return sBluetoothA2dpManager;
-}
-
-void
-BluetoothA2dpManager::HandleShutdown()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  sInShutdown = true;
-  Disconnect(nullptr);
-  sBluetoothA2dpManager = nullptr;
-}
-
-void
-BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress,
-                              BluetoothProfileController* aController)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
-  MOZ_ASSERT(aController && !mController);
-
-  BluetoothService* bs = BluetoothService::Get();
-  if (!bs || sInShutdown) {
-    aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
-    return;
-  }
-
-  if (mA2dpConnected) {
-    aController->OnConnect(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED));
-    return;
-  }
-
-  mDeviceAddress = aDeviceAddress;
-  mController = aController;
-
-  if (NS_FAILED(bs->SendSinkMessage(aDeviceAddress,
-                                    NS_LITERAL_STRING("Connect")))) {
-    aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
-    return;
-  }
-}
-
-void
-BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController)
-{
-  BluetoothService* bs = BluetoothService::Get();
-  if (!bs) {
-    if (aController) {
-      aController->OnDisconnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
-    }
-    return;
-  }
-
-  if (!mA2dpConnected) {
-    if (aController) {
-      aController->OnDisconnect(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED));
-    }
-    return;
-  }
-
-  MOZ_ASSERT(!mDeviceAddress.IsEmpty());
-  MOZ_ASSERT(!mController);
-
-  mController = aController;
-
-  if (NS_FAILED(bs->SendSinkMessage(mDeviceAddress,
-                                    NS_LITERAL_STRING("Disconnect")))) {
-    aController->OnDisconnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
-    return;
-  }
-}
-
-void
-BluetoothA2dpManager::OnConnect(const nsAString& aErrorStr)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  /**
-   * On the one hand, notify the controller that we've done for outbound
-   * connections. On the other hand, we do nothing for inbound connections.
-   */
-  NS_ENSURE_TRUE_VOID(mController);
-
-  nsRefPtr<BluetoothProfileController> controller = mController.forget();
-  controller->OnConnect(aErrorStr);
-}
-
-void
-BluetoothA2dpManager::OnDisconnect(const nsAString& aErrorStr)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  /**
-   * On the one hand, notify the controller that we've done for outbound
-   * connections. On the other hand, we do nothing for inbound connections.
-   */
-  NS_ENSURE_TRUE_VOID(mController);
-
-  nsRefPtr<BluetoothProfileController> controller = mController.forget();
-  controller->OnDisconnect(aErrorStr);
-}
-
-/* HandleSinkPropertyChanged update sink state in A2dp
- *
- * Possible values: "disconnected", "connecting", "connected", "playing"
- *
- * 1. "disconnected" -> "connecting"
- *    Either an incoming or outgoing connection attempt ongoing
- * 2. "connecting" -> "disconnected"
- *    Connection attempt failed
- * 3. "connecting" -> "connected"
- *    Successfully connected
- * 4. "connected" -> "playing"
- *    Audio stream active
- * 5. "playing" -> "connected"
- *    Audio stream suspended
- * 6. "connected" -> "disconnected"
- *    "playing" -> "disconnected"
- *    Disconnected from local or the remote device
- */
-void
-BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal)
-{
-  MOZ_ASSERT(NS_IsMainThread());
-  MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue);
-
-  const nsString& address = aSignal.path();
-  const InfallibleTArray<BluetoothNamedValue>& arr =
-    aSignal.value().get_ArrayOfBluetoothNamedValue();
-  MOZ_ASSERT(arr.Length() == 1);
-
-  /**
-   * There are three properties:
-   * - "State": a string
-   * - "Connected": a boolean value
-   * - "Playing": a boolean value
-   *
-   * Note that only "State" is handled in this function.
-   */
-
-  const nsString& name = arr[0].name();
-  NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State"));
-
-  const BluetoothValue& value = arr[0].value();
-  MOZ_ASSERT(value.type() == BluetoothValue::TnsString);
-  SinkState newState = StatusStringToSinkState(value.get_nsString());
-  NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) &&
-                      (newState != mSinkState));
-
-  SinkState prevState = mSinkState;
-  mSinkState = newState;
-
-  switch(mSinkState) {
-    case SinkState::SINK_CONNECTING:
-      // case 1: Either an incoming or outgoing connection attempt ongoing
-      MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED);
-      break;
-    case SinkState::SINK_PLAYING:
-      // case 4: Audio stream active
-      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED);
-      break;
-    case SinkState::SINK_CONNECTED:
-      // case 5: Audio stream suspended
-      if (prevState == SinkState::SINK_PLAYING) {
-        break;
-      }
-      
-      // case 3: Successfully connected
-      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTING);
-
-      mA2dpConnected = true;
-      mDeviceAddress = address;
-      NotifyConnectionStatusChanged();
-
-      OnConnect(EmptyString());
-      break;
-    case SinkState::SINK_DISCONNECTED:
-      // case 2: Connection attempt failed
-      if (prevState == SinkState::SINK_CONNECTING) {
-        OnConnect(NS_LITERAL_STRING("A2dpConnectionError"));
-        break;
-      }
-
-      // case 6: Disconnected from the remote device
-      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED ||
-                 prevState == SinkState::SINK_PLAYING) ;
-
-      mA2dpConnected = false;
-      NotifyConnectionStatusChanged();
-      mDeviceAddress.Truncate();
-      OnDisconnect(EmptyString());
-      break;
-    default:
-      break;
-  }
-}
-
-void
-BluetoothA2dpManager::NotifyConnectionStatusChanged()
-{
-  MOZ_ASSERT(NS_IsMainThread());
-
-  // Notify Gecko observers
-  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
-  NS_ENSURE_TRUE_VOID(obs);
-
-  if (NS_FAILED(obs->NotifyObservers(this,
-                                     BLUETOOTH_A2DP_STATUS_CHANGED_ID,
-                                     mDeviceAddress.get()))) {
-    BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!");
-  }
-
-  // Dispatch an event of status change
-  DispatchStatusChangedEvent(
-    NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected);
-}
-
-void
-BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
-                                          const nsAString& aServiceUuid,
-                                          int aChannel)
-{
-}
-
-void
-BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
-{
-}
-
-void
-BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress)
-{
-  aDeviceAddress = mDeviceAddress;
-}
-
-bool
-BluetoothA2dpManager::IsConnected()
-{
-  return mA2dpConnected;
-}
-
-void
-BluetoothA2dpManager::SetAvrcpConnected(bool aConnected)
-{
-  mAvrcpConnected = aConnected;
-  if (!aConnected) {
-    ResetAvrcp();
-  }
-}
-
-bool
-BluetoothA2dpManager::IsAvrcpConnected()
-{
-  return mAvrcpConnected;
-}
-
-void
-BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle,
-                                     const nsAString& aArtist,
-                                     const nsAString& aAlbum,
-                                     uint32_t aMediaNumber,
-                                     uint32_t aTotalMediaCount,
-                                     uint32_t aDuration)
-{
-  mTitle.Assign(aTitle);
-  mArtist.Assign(aArtist);
-  mAlbum.Assign(aAlbum);
-  mMediaNumber = aMediaNumber;
-  mTotalMediaCount = aTotalMediaCount;
-  mDuration = aDuration;
-}
-
-void
-BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration,
-                                       uint32_t aPosition,
-                                       ControlPlayStatus aPlayStatus)
-{
-  mDuration = aDuration;
-  mPosition = aPosition;
-  mPlayStatus = aPlayStatus;
-}
-
-void
-BluetoothA2dpManager::GetAlbum(nsAString& aAlbum)
-{
-    aAlbum.Assign(mAlbum);
-}
-
-uint32_t
-BluetoothA2dpManager::GetDuration()
-{
-  return mDuration;
-}
-
-ControlPlayStatus
-BluetoothA2dpManager::GetPlayStatus()
-{
-  return mPlayStatus;
-}
-
-uint32_t
-BluetoothA2dpManager::GetPosition()
-{
-  return mPosition;
-}
-
-uint32_t
-BluetoothA2dpManager::GetMediaNumber()
-{
-  return mMediaNumber;
-}
-
-void
-BluetoothA2dpManager::GetTitle(nsAString& aTitle)
-{
-  aTitle.Assign(mTitle);
-}
-
-NS_IMPL_ISUPPORTS1(BluetoothA2dpManager, nsIObserver)
-
deleted file mode 100644
--- a/dom/bluetooth/BluetoothA2dpManager.h
+++ /dev/null
@@ -1,90 +0,0 @@
-/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
-/* vim: set ts=2 et sw=2 tw=80: */
-/* This Source Code Form is subject to the terms of the Mozilla Public
- * License, v. 2.0. If a copy of the MPL was not distributed with this file,
- * You can obtain one at http://mozilla.org/MPL/2.0/. */
-
-#ifndef mozilla_dom_bluetooth_bluetootha2dpmanager_h__
-#define mozilla_dom_bluetooth_bluetootha2dpmanager_h__
-
-#include "BluetoothCommon.h"
-#include "BluetoothProfileController.h"
-#include "BluetoothProfileManagerBase.h"
-
-BEGIN_BLUETOOTH_NAMESPACE
-
-class BluetoothA2dpManager : public BluetoothProfileManagerBase
-{
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-  BT_DECL_PROFILE_MGR_BASE
-  virtual void GetName(nsACString& aName)
-  {
-    aName.AssignLiteral("A2DP");
-  }
-
-  enum SinkState {
-    SINK_UNKNOWN,
-    SINK_DISCONNECTED,
-    SINK_CONNECTING,
-    SINK_CONNECTED,
-    SINK_PLAYING,
-  };
-
-  static BluetoothA2dpManager* Get();
-  ~BluetoothA2dpManager();
-  void ResetA2dp();
-  void ResetAvrcp();
-
-  // A2DP-specific functions
-  void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
-
-  // AVRCP-specific functions
-  void SetAvrcpConnected(bool aConnected);
-  bool IsAvrcpConnected();
-  void UpdateMetaData(const nsAString& aTitle,
-                      const nsAString& aArtist,
-                      const nsAString& aAlbum,
-                      uint32_t aMediaNumber,
-                      uint32_t aTotalMediaCount,
-                      uint32_t aDuration);
-  void UpdatePlayStatus(uint32_t aDuration,
-                        uint32_t aPosition,
-                        ControlPlayStatus aPlayStatus);
-  void GetAlbum(nsAString& aAlbum);
-  uint32_t GetDuration();
-  ControlPlayStatus GetPlayStatus();
-  uint32_t GetPosition();
-  uint32_t GetMediaNumber();
-  void GetTitle(nsAString& aTitle);
-
-private:
-  BluetoothA2dpManager();
-  bool Init();
-
-  void HandleShutdown();
-  void NotifyConnectionStatusChanged();
-
-  nsString mDeviceAddress;
-  nsRefPtr<BluetoothProfileController> mController;
-
-  // A2DP data member
-  bool mA2dpConnected;
-  SinkState mSinkState;
-
-  // AVRCP data member
-  bool mAvrcpConnected;
-  nsString mAlbum;
-  nsString mArtist;
-  nsString mTitle;
-  uint32_t mDuration;
-  uint32_t mMediaNumber;
-  uint32_t mTotalMediaCount;
-  uint32_t mPosition;
-  ControlPlayStatus mPlayStatus;
-};
-
-END_BLUETOOTH_NAMESPACE
-
-#endif
--- a/dom/bluetooth/BluetoothService.cpp
+++ b/dom/bluetooth/BluetoothService.cpp
@@ -141,38 +141,28 @@ public:
   {
     MOZ_ASSERT(!NS_IsMainThread());
   }
 
   NS_IMETHOD Run()
   {
     MOZ_ASSERT(NS_IsMainThread());
 
-    if (!gBluetoothService) {
+    NS_ENSURE_TRUE(gBluetoothService, NS_OK);
+
+    if (gInShutdown) {
+      gBluetoothService = nullptr;
       return NS_OK;
     }
 
-    if (!gInShutdown) {
-      gBluetoothService->SetEnabled(mEnabled);
-
-      nsAutoString signalName, signalPath;
-      BluetoothValue v = true;
-      if (mEnabled) {
-        signalName = NS_LITERAL_STRING("Enabled");
-      } else {
-        signalName = NS_LITERAL_STRING("Disabled");
-      }
-      signalPath = NS_LITERAL_STRING(KEY_MANAGER);
-      BluetoothSignal signal(signalName, signalPath, v);
-      gBluetoothService->DistributeSignal(signal);
-    }
-
-    if (gInShutdown) {
-      gBluetoothService = nullptr;
-    }
+    nsAutoString signalName;
+    signalName = mEnabled ? NS_LITERAL_STRING("Enabled")
+                          : NS_LITERAL_STRING("Disabled");
+    BluetoothSignal signal(signalName, NS_LITERAL_STRING(KEY_MANAGER), true);
+    gBluetoothService->DistributeSignal(signal);
 
     return NS_OK;
   }
 
 private:
   bool mEnabled;
 };
 
@@ -213,16 +203,20 @@ public:
       } else {
         if (NS_FAILED(gBluetoothService->StopInternal())) {
           BT_WARNING("Bluetooth service failed to stop!");
           mEnabled = !mEnabled;
         }
       }
     }
 
+    // Update mEnabled of BluetoothService object since
+    // StartInternal/StopInternal have been already done.
+    gBluetoothService->SetEnabled(mEnabled);
+
     // This is requested in Bug 836516. With settings this property, WLAN
     // firmware could be aware of Bluetooth has been turned on/off, so that the
     // mecahnism of handling coexistence of WIFI and Bluetooth could be started.
     //
     // In the future, we may have our own way instead of setting a system
     // property to let firmware developers be able to sense that Bluetooth has
     // been toggled.
 #if defined(MOZ_WIDGET_GONK)
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.cpp
@@ -0,0 +1,568 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "BluetoothA2dpManager.h"
+
+#include <hardware/bluetooth.h>
+#include <hardware/bt_av.h>
+
+#include "BluetoothCommon.h"
+#include "BluetoothService.h"
+#include "BluetoothServiceBluedroid.h"
+#include "BluetoothSocket.h"
+#include "BluetoothUtils.h"
+
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "MainThreadUtils.h"
+#include "nsIObserverService.h"
+#include "nsThreadUtils.h"
+
+using namespace mozilla;
+USING_BLUETOOTH_NAMESPACE
+
+namespace {
+  StaticRefPtr<BluetoothA2dpManager> sBluetoothA2dpManager;
+  bool sInShutdown = false;
+  static const btav_interface_t* sBtA2dpInterface;
+} // anonymous namespace
+
+
+class SinkPropertyChangedHandler : public nsRunnable
+{
+public:
+  SinkPropertyChangedHandler(const BluetoothSignal& aSignal)
+    : mSignal(aSignal)
+  {
+  }
+
+  NS_IMETHOD
+  Run()
+  {
+    MOZ_ASSERT(NS_IsMainThread());
+
+    BluetoothA2dpManager* a2dp = BluetoothA2dpManager::Get();
+    NS_ENSURE_TRUE(a2dp, NS_ERROR_FAILURE);
+    a2dp->HandleSinkPropertyChanged(mSignal);
+    return NS_OK;
+  }
+
+private:
+  BluetoothSignal mSignal;
+};
+
+NS_IMETHODIMP
+BluetoothA2dpManager::Observe(nsISupports* aSubject,
+                              const char* aTopic,
+                              const PRUnichar* aData)
+{
+  MOZ_ASSERT(sBluetoothA2dpManager);
+
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    HandleShutdown();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+BluetoothA2dpManager::BluetoothA2dpManager()
+{
+  ResetA2dp();
+  ResetAvrcp();
+}
+
+static void
+AvStatusToSinkString(btav_connection_state_t aStatus, nsAString& aState)
+{
+  nsAutoString state;
+  if (aStatus == BTAV_CONNECTION_STATE_DISCONNECTED) {
+    aState = NS_LITERAL_STRING("disconnected");
+  } else if (aStatus == BTAV_CONNECTION_STATE_CONNECTING) {
+    aState = NS_LITERAL_STRING("connecting");
+  } else if (aStatus == BTAV_CONNECTION_STATE_CONNECTED) {
+    aState = NS_LITERAL_STRING("connected");
+  } else if (aStatus == BTAV_CONNECTION_STATE_DISCONNECTING) {
+    aState = NS_LITERAL_STRING("disconnecting");
+  } else {
+    BT_WARNING("Unknown sink state");
+  }
+}
+
+static void
+A2dpConnectionStateCallback(btav_connection_state_t aState,
+                            bt_bdaddr_t* aBdAddress)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsString remoteDeviceBdAddress;
+  BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress);
+
+  nsString a2dpState;
+  AvStatusToSinkString(aState, a2dpState);
+
+  InfallibleTArray<BluetoothNamedValue> props;
+  props.AppendElement(
+    BluetoothNamedValue(NS_LITERAL_STRING("State"), a2dpState));
+
+  BluetoothSignal signal(NS_LITERAL_STRING("AudioSink"),
+                         remoteDeviceBdAddress, props);
+  NS_DispatchToMainThread(new SinkPropertyChangedHandler(signal));
+}
+
+static void
+A2dpAudioStateCallback(btav_audio_state_t aState,
+                       bt_bdaddr_t* aBdAddress)
+{
+  MOZ_ASSERT(!NS_IsMainThread());
+
+  nsString remoteDeviceBdAddress;
+  BdAddressTypeToString(aBdAddress, remoteDeviceBdAddress);
+
+  nsString a2dpState;
+
+  if (aState == BTAV_AUDIO_STATE_STARTED) {
+    a2dpState = NS_LITERAL_STRING("playing");
+  } else if (aState == BTAV_AUDIO_STATE_STOPPED) {
+    // for avdtp state stop stream
+    a2dpState = NS_LITERAL_STRING("connected");
+  } else if (aState == BTAV_AUDIO_STATE_REMOTE_SUSPEND) {
+    // for avdtp state suspend stream from remote side
+    a2dpState = NS_LITERAL_STRING("connected");
+  }
+
+  InfallibleTArray<BluetoothNamedValue> props;
+  props.AppendElement(
+    BluetoothNamedValue(NS_LITERAL_STRING("State"), a2dpState));
+
+  BluetoothSignal signal(NS_LITERAL_STRING("AudioSink"),
+                         remoteDeviceBdAddress, props);
+  NS_DispatchToMainThread(new SinkPropertyChangedHandler(signal));
+}
+
+static btav_callbacks_t sBtA2dpCallbacks = {
+  sizeof(sBtA2dpCallbacks),
+  A2dpConnectionStateCallback,
+  A2dpAudioStateCallback
+};
+
+/*
+ * This function will be only called when Bluetooth is turning on.
+ * It is important to register a2dp callbacks before enable() gets called.
+ * It is required to register a2dp callbacks before a2dp media task
+ * starts up.
+ */
+bool
+BluetoothA2dpManager::Init()
+{
+  const bt_interface_t* btInf = GetBluetoothInterface();
+  NS_ENSURE_TRUE(btInf, false);
+  sBtA2dpInterface = (btav_interface_t *)btInf->
+    get_profile_interface(BT_PROFILE_ADVANCED_AUDIO_ID);
+  NS_ENSURE_TRUE(sBtA2dpInterface, false);
+
+  int ret = sBtA2dpInterface->init(&sBtA2dpCallbacks);
+  if (ret != BT_STATUS_SUCCESS) {
+    BT_LOGR("failed to init a2dp module");
+    return false;
+  }
+  return true;
+}
+
+BluetoothA2dpManager::~BluetoothA2dpManager()
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obs);
+  if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
+    BT_WARNING("Failed to remove shutdown observer!");
+  }
+}
+
+void
+BluetoothA2dpManager::ResetA2dp()
+{
+  mA2dpConnected = false;
+  mSinkState = SinkState::SINK_DISCONNECTED;
+  mController = nullptr;
+}
+
+void
+BluetoothA2dpManager::ResetAvrcp()
+{
+  mAvrcpConnected = false;
+  mDuration = 0;
+  mMediaNumber = 0;
+  mTotalMediaCount = 0;
+  mPosition = 0;
+  mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
+}
+
+/*
+ * Static functions
+ */
+
+static BluetoothA2dpManager::SinkState
+StatusStringToSinkState(const nsAString& aStatus)
+{
+  BluetoothA2dpManager::SinkState state =
+    BluetoothA2dpManager::SinkState::SINK_UNKNOWN;
+  if (aStatus.EqualsLiteral("disconnected")) {
+    state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED;
+  } else if (aStatus.EqualsLiteral("connecting")) {
+    state = BluetoothA2dpManager::SinkState::SINK_CONNECTING;
+  } else if (aStatus.EqualsLiteral("connected")) {
+    state = BluetoothA2dpManager::SinkState::SINK_CONNECTED;
+  } else if (aStatus.EqualsLiteral("playing")) {
+    state = BluetoothA2dpManager::SinkState::SINK_PLAYING;
+  } else {
+    BT_WARNING("Unknown sink state");
+  }
+  return state;
+}
+
+//static
+BluetoothA2dpManager*
+BluetoothA2dpManager::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If sBluetoothA2dpManager already exists, exit early
+  if (sBluetoothA2dpManager) {
+    return sBluetoothA2dpManager;
+  }
+
+  // If we're in shutdown, don't create a new instance
+  NS_ENSURE_FALSE(sInShutdown, nullptr);
+
+  // Create a new instance, register, and return
+  BluetoothA2dpManager* manager = new BluetoothA2dpManager();
+  NS_ENSURE_TRUE(manager->Init(), nullptr);
+
+  sBluetoothA2dpManager = manager;
+  return sBluetoothA2dpManager;
+}
+
+void
+BluetoothA2dpManager::HandleShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  sInShutdown = true;
+  Disconnect(nullptr);
+  sBluetoothA2dpManager = nullptr;
+}
+
+void
+BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress,
+                              BluetoothProfileController* aController)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
+  MOZ_ASSERT(aController && !mController);
+
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || sInShutdown) {
+    aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    return;
+  }
+
+  if (mA2dpConnected) {
+    aController->OnConnect(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED));
+    return;
+  }
+
+  mDeviceAddress = aDeviceAddress;
+  mController = aController;
+
+  bt_bdaddr_t remoteAddress;
+  StringToBdAddressType(aDeviceAddress, &remoteAddress);
+  NS_ENSURE_TRUE_VOID(sBtA2dpInterface);
+  NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
+                      sBtA2dpInterface->connect(&remoteAddress));
+}
+
+void
+BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController)
+{
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs) {
+    if (aController) {
+      aController->OnDisconnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    }
+    return;
+  }
+
+  if (!mA2dpConnected) {
+    if (aController) {
+      aController->OnDisconnect(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED));
+    }
+    return;
+  }
+
+  MOZ_ASSERT(!mDeviceAddress.IsEmpty());
+  MOZ_ASSERT(!mController);
+
+  mController = aController;
+
+  bt_bdaddr_t remoteAddress;
+  StringToBdAddressType(mDeviceAddress, &remoteAddress);
+  if (sBtA2dpInterface) {
+    NS_ENSURE_TRUE_VOID(BT_STATUS_SUCCESS ==
+                        sBtA2dpInterface->disconnect(&remoteAddress));
+  }
+}
+
+void
+BluetoothA2dpManager::OnConnect(const nsAString& aErrorStr)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * On the one hand, notify the controller that we've done for outbound
+   * connections. On the other hand, we do nothing for inbound connections.
+   */
+  NS_ENSURE_TRUE_VOID(mController);
+
+  nsRefPtr<BluetoothProfileController> controller = mController.forget();
+  controller->OnConnect(aErrorStr);
+}
+
+void
+BluetoothA2dpManager::OnDisconnect(const nsAString& aErrorStr)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * On the one hand, notify the controller that we've done for outbound
+   * connections. On the other hand, we do nothing for inbound connections.
+   */
+  NS_ENSURE_TRUE_VOID(mController);
+
+  nsRefPtr<BluetoothProfileController> controller = mController.forget();
+  controller->OnDisconnect(aErrorStr);
+}
+
+/* HandleSinkPropertyChanged update sink state in A2dp
+ *
+ * Possible values: "disconnected", "connecting", "connected", "playing"
+ *
+ * 1. "disconnected" -> "connecting"
+ *    Either an incoming or outgoing connection attempt ongoing
+ * 2. "connecting" -> "disconnected"
+ *    Connection attempt failed
+ * 3. "connecting" -> "connected"
+ *    Successfully connected
+ * 4. "connected" -> "playing"
+ *    Audio stream active
+ * 5. "playing" -> "connected"
+ *    Audio stream suspended
+ * 6. "connected" -> "disconnected"
+ *    "playing" -> "disconnected"
+ *    Disconnected from local or the remote device
+ */
+void
+BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSignal.value().type() ==
+             BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const nsString& address = aSignal.path();
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aSignal.value().get_ArrayOfBluetoothNamedValue();
+  MOZ_ASSERT(arr.Length() == 1);
+
+  /**
+   * There are three properties:
+   * - "State": a string
+   * - "Connected": a boolean value
+   * - "Playing": a boolean value
+   *
+   * Note that only "State" is handled in this function.
+   */
+
+  const nsString& name = arr[0].name();
+  NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State"));
+
+  const BluetoothValue& value = arr[0].value();
+  MOZ_ASSERT(value.type() == BluetoothValue::TnsString);
+  SinkState newState = StatusStringToSinkState(value.get_nsString());
+  NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) &&
+                      (newState != mSinkState));
+
+  SinkState prevState = mSinkState;
+  mSinkState = newState;
+
+  switch(mSinkState) {
+    case SinkState::SINK_CONNECTING:
+      // case 1: Either an incoming or outgoing connection attempt ongoing
+      MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED);
+      break;
+    case SinkState::SINK_PLAYING:
+      // case 4: Audio stream active
+      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED);
+      break;
+    case SinkState::SINK_CONNECTED:
+      // case 5: Audio stream suspended
+      if (prevState == SinkState::SINK_PLAYING ||
+          prevState == SinkState::SINK_CONNECTED) {
+        break;
+      }
+
+      // case 3: Successfully connected
+      mA2dpConnected = true;
+      mDeviceAddress = address;
+      NotifyConnectionStatusChanged();
+
+      OnConnect(EmptyString());
+      break;
+    case SinkState::SINK_DISCONNECTED:
+      // case 2: Connection attempt failed
+      if (prevState == SinkState::SINK_CONNECTING) {
+        OnConnect(NS_LITERAL_STRING("A2dpConnectionError"));
+        break;
+      }
+
+      // case 6: Disconnected from the remote device
+      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED ||
+                 prevState == SinkState::SINK_PLAYING) ;
+
+      mA2dpConnected = false;
+      NotifyConnectionStatusChanged();
+      mDeviceAddress.Truncate();
+      OnDisconnect(EmptyString());
+      break;
+    default:
+      break;
+  }
+}
+
+void
+BluetoothA2dpManager::NotifyConnectionStatusChanged()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Notify Gecko observers
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obs);
+
+  if (NS_FAILED(obs->NotifyObservers(this,
+                                     BLUETOOTH_A2DP_STATUS_CHANGED_ID,
+                                     mDeviceAddress.get()))) {
+    BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!");
+  }
+
+  // Dispatch an event of status change
+  DispatchStatusChangedEvent(
+    NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected);
+}
+
+void
+BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                          const nsAString& aServiceUuid,
+                                          int aChannel)
+{
+}
+
+void
+BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
+{
+}
+
+void
+BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress)
+{
+  aDeviceAddress = mDeviceAddress;
+}
+
+bool
+BluetoothA2dpManager::IsConnected()
+{
+  return mA2dpConnected;
+}
+
+void
+BluetoothA2dpManager::SetAvrcpConnected(bool aConnected)
+{
+  mAvrcpConnected = aConnected;
+  if (!aConnected) {
+    ResetAvrcp();
+  }
+}
+
+bool
+BluetoothA2dpManager::IsAvrcpConnected()
+{
+  return mAvrcpConnected;
+}
+
+void
+BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle,
+                                     const nsAString& aArtist,
+                                     const nsAString& aAlbum,
+                                     uint32_t aMediaNumber,
+                                     uint32_t aTotalMediaCount,
+                                     uint32_t aDuration)
+{
+  mTitle.Assign(aTitle);
+  mArtist.Assign(aArtist);
+  mAlbum.Assign(aAlbum);
+  mMediaNumber = aMediaNumber;
+  mTotalMediaCount = aTotalMediaCount;
+  mDuration = aDuration;
+}
+
+void
+BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration,
+                                       uint32_t aPosition,
+                                       ControlPlayStatus aPlayStatus)
+{
+  mDuration = aDuration;
+  mPosition = aPosition;
+  mPlayStatus = aPlayStatus;
+}
+
+void
+BluetoothA2dpManager::GetAlbum(nsAString& aAlbum)
+{
+    aAlbum.Assign(mAlbum);
+}
+
+uint32_t
+BluetoothA2dpManager::GetDuration()
+{
+  return mDuration;
+}
+
+ControlPlayStatus
+BluetoothA2dpManager::GetPlayStatus()
+{
+  return mPlayStatus;
+}
+
+uint32_t
+BluetoothA2dpManager::GetPosition()
+{
+  return mPosition;
+}
+
+uint32_t
+BluetoothA2dpManager::GetMediaNumber()
+{
+  return mMediaNumber;
+}
+
+void
+BluetoothA2dpManager::GetTitle(nsAString& aTitle)
+{
+  aTitle.Assign(mTitle);
+}
+
+NS_IMPL_ISUPPORTS1(BluetoothA2dpManager, nsIObserver)
+
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluedroid/BluetoothA2dpManager.h
@@ -0,0 +1,90 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_bluetootha2dpmanager_h__
+#define mozilla_dom_bluetooth_bluetootha2dpmanager_h__
+
+#include "BluetoothCommon.h"
+#include "BluetoothProfileController.h"
+#include "BluetoothProfileManagerBase.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothA2dpManager : public BluetoothProfileManagerBase
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  BT_DECL_PROFILE_MGR_BASE
+  virtual void GetName(nsACString& aName)
+  {
+    aName.AssignLiteral("A2DP");
+  }
+
+  enum SinkState {
+    SINK_UNKNOWN,
+    SINK_DISCONNECTED,
+    SINK_CONNECTING,
+    SINK_CONNECTED,
+    SINK_PLAYING,
+  };
+
+  static BluetoothA2dpManager* Get();
+  ~BluetoothA2dpManager();
+  void ResetA2dp();
+  void ResetAvrcp();
+
+  // A2DP-specific functions
+  void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
+
+  // AVRCP-specific functions
+  void SetAvrcpConnected(bool aConnected);
+  bool IsAvrcpConnected();
+  void UpdateMetaData(const nsAString& aTitle,
+                      const nsAString& aArtist,
+                      const nsAString& aAlbum,
+                      uint32_t aMediaNumber,
+                      uint32_t aTotalMediaCount,
+                      uint32_t aDuration);
+  void UpdatePlayStatus(uint32_t aDuration,
+                        uint32_t aPosition,
+                        ControlPlayStatus aPlayStatus);
+  void GetAlbum(nsAString& aAlbum);
+  uint32_t GetDuration();
+  ControlPlayStatus GetPlayStatus();
+  uint32_t GetPosition();
+  uint32_t GetMediaNumber();
+  void GetTitle(nsAString& aTitle);
+
+private:
+  class SinkPropertyChangedHandler;
+  BluetoothA2dpManager();
+  bool Init();
+  void HandleShutdown();
+  void NotifyConnectionStatusChanged();
+
+  nsString mDeviceAddress;
+  nsRefPtr<BluetoothProfileController> mController;
+
+  // A2DP data member
+  bool mA2dpConnected;
+  SinkState mSinkState;
+
+  // AVRCP data member
+  bool mAvrcpConnected;
+  nsString mAlbum;
+  nsString mArtist;
+  nsString mTitle;
+  uint32_t mDuration;
+  uint32_t mMediaNumber;
+  uint32_t mTotalMediaCount;
+  uint32_t mPosition;
+  ControlPlayStatus mPlayStatus;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp
+++ b/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.cpp
@@ -15,22 +15,26 @@
 ** See the License for the specific language governing permissions and
 ** limitations under the License.
 */
 
 #include "BluetoothServiceBluedroid.h"
 
 #include <hardware/hardware.h>
 
+#include "bluedroid/BluetoothA2dpManager.h"
+#include "bluedroid/BluetoothHfpManager.h"
 #include "BluetoothProfileController.h"
 #include "BluetoothReplyRunnable.h"
 #include "BluetoothUtils.h"
 #include "BluetoothUuid.h"
 #include "mozilla/dom/bluetooth/BluetoothTypes.h"
 #include "mozilla/ipc/UnixSocket.h"
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPtr.h"
 
 using namespace mozilla;
 using namespace mozilla::ipc;
 USING_BLUETOOTH_NAMESPACE
 
 /**
  *  Classes only used in this file
  */
@@ -70,16 +74,17 @@ static InfallibleTArray<nsString> sAdapt
 static InfallibleTArray<BluetoothNamedValue> sRemoteDevicesPack;
 static nsTArray<nsRefPtr<BluetoothProfileController> > sControllerArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sBondingRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sChangeDiscoveryRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sGetPairedDeviceRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sSetPropertyRunnableArray;
 static nsTArray<nsRefPtr<BluetoothReplyRunnable> > sUnbondingRunnableArray;
 static nsTArray<int> sRequestedDeviceCountArray;
+static StaticAutoPtr<Monitor> sToggleBtMonitor;
 
 /**
  *  Static callback functions
  */
 static void
 ClassToIcon(uint32_t aClass, nsAString& aRetIcon)
 {
   switch ((aClass & 0x1f00) >> 8) {
@@ -164,32 +169,32 @@ IsReady()
     return false;
   }
   return true;
 }
 
 const bt_interface_t*
 GetBluetoothInterface()
 {
-  return (IsReady()) ? sBtInterface : nullptr;
+  return sBtInterface;
 }
 
 void
 StringToBdAddressType(const nsAString& aBdAddress,
                       bt_bdaddr_t *aRetBdAddressType)
 {
   const char* str = NS_ConvertUTF16toUTF8(aBdAddress).get();
 
   for (int i = 0; i < 6; i++) {
     aRetBdAddressType->address[i] = (uint8_t) strtoul(str, (char **)&str, 16);
     str++;
   }
 }
 
-static void
+void
 BdAddressTypeToString(bt_bdaddr_t* aBdAddressType, nsAString& aRetBdAddress)
 {
   uint8_t* addr = aBdAddressType->address;
   bdstr_t bdstr;
 
   sprintf((char*)bdstr, "%02x:%02x:%02x:%02x:%02x:%02x",
           (int)addr[0],(int)addr[1],(int)addr[2],
           (int)addr[3],(int)addr[4],(int)addr[5]);
@@ -208,41 +213,44 @@ Setup()
   prop.len = sizeof(mode);
 
   NS_ENSURE_TRUE_VOID(sBtInterface);
 
   int ret = sBtInterface->set_adapter_property(&prop);
   if (ret != BT_STATUS_SUCCESS) {
     BT_LOGR("%s: Fail to set: BT_SCAN_MODE_CONNECTABLE", __FUNCTION__);
   }
+
+  // Event 'AdapterAdded' has to be fired after enabled to notify Gaia
+  // that BluetoothAdapter is ready.
+  BluetoothSignal signal(NS_LITERAL_STRING("AdapterAdded"),
+                         NS_LITERAL_STRING(KEY_MANAGER), true);
+  nsRefPtr<DistributeBluetoothSignalTask>
+    t = new DistributeBluetoothSignalTask(signal);
+  if (NS_FAILED(NS_DispatchToMainThread(t))) {
+    BT_WARNING("Failed to dispatch to main thread!");
+  }
 }
 
 static void
 AdapterStateChangeCallback(bt_state_t aStatus)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   BT_LOGR("%s, BT_STATE:%d", __FUNCTION__, aStatus);
 
-  nsAutoString signalName;
-  if (aStatus == BT_STATE_ON) {
-    Setup();
-    sIsBtEnabled = true;
-    signalName = NS_LITERAL_STRING("AdapterAdded");
-  } else {
-    sIsBtEnabled = false;
-    signalName = NS_LITERAL_STRING("Disabled");
+  sIsBtEnabled = (aStatus == BT_STATE_ON);
+
+  {
+    MonitorAutoLock lock(*sToggleBtMonitor);
+    lock.Notify();
   }
 
-  BluetoothSignal signal(signalName, NS_LITERAL_STRING(KEY_MANAGER),
-                         BluetoothValue(true));
-  nsRefPtr<DistributeBluetoothSignalTask>
-    t = new DistributeBluetoothSignalTask(signal);
-  if (NS_FAILED(NS_DispatchToMainThread(t))) {
-    NS_WARNING("Failed to dispatch to main thread!");
+  if (sIsBtEnabled) {
+    Setup();
   }
 }
 
 /**
  * AdapterPropertiesChangeCallback will be called after enable() but before
  * AdapterStateChangeCallback sIsBtEnabled get updated.
  * At that moment, both BluetoothManager/BluetoothAdapter does not register
  * observer yet.
@@ -316,17 +324,17 @@ AdapterPropertiesChangeCallback(bt_statu
     }
 
     BluetoothValue value(propertiesArray);
     BluetoothSignal signal(NS_LITERAL_STRING("PropertyChanged"),
                            NS_LITERAL_STRING(KEY_ADAPTER), value);
     nsRefPtr<DistributeBluetoothSignalTask>
       t = new DistributeBluetoothSignalTask(signal);
     if (NS_FAILED(NS_DispatchToMainThread(t))) {
-      NS_WARNING("Failed to dispatch to main thread!");
+      BT_WARNING("Failed to dispatch to main thread!");
     }
 
     // bluedroid BTU task was stored in the task queue, see GKI_send_msg
     if (!sSetPropertyRunnableArray.IsEmpty()) {
       DispatchBluetoothReply(sSetPropertyRunnableArray[0], BluetoothValue(true),
                              EmptyString());
       sSetPropertyRunnableArray.RemoveElementAt(0);
     }
@@ -442,17 +450,17 @@ DeviceFoundCallback(int aNumProperties, 
   }
 
   BluetoothValue value = propertiesArray;
   BluetoothSignal signal(NS_LITERAL_STRING("DeviceFound"),
                          NS_LITERAL_STRING(KEY_ADAPTER), value);
   nsRefPtr<DistributeBluetoothSignalTask>
     t = new DistributeBluetoothSignalTask(signal);
   if (NS_FAILED(NS_DispatchToMainThread(t))) {
-    NS_WARNING("Failed to dispatch to main thread!");
+    BT_WARNING("Failed to dispatch to main thread!");
   }
 }
 
 static void
 DiscoveryStateChangedCallback(bt_discovery_state_t aState)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
@@ -486,17 +494,17 @@ PinRequestCallback(bt_bdaddr_t* aRemoteB
                           (const char*)aRemoteBdName->name)));
 
   BluetoothValue value = propertiesArray;
   BluetoothSignal signal(NS_LITERAL_STRING("RequestPinCode"),
                          NS_LITERAL_STRING(KEY_LOCAL_AGENT), value);
   nsRefPtr<DistributeBluetoothSignalTask>
     t = new DistributeBluetoothSignalTask(signal);
   if (NS_FAILED(NS_DispatchToMainThread(t))) {
-    NS_WARNING("Failed to dispatch to main thread!");
+    BT_WARNING("Failed to dispatch to main thread!");
   }
 }
 
 static void
 SspRequestCallback(bt_bdaddr_t* aRemoteBdAddress, bt_bdname_t* aRemoteBdName,
                    uint32_t aRemoteClass, bt_ssp_variant_t aPairingVariant,
                    uint32_t aPasskey)
 {
@@ -519,17 +527,17 @@ SspRequestCallback(bt_bdaddr_t* aRemoteB
     BluetoothNamedValue(NS_LITERAL_STRING("passkey"), aPasskey));
 
   BluetoothValue value = propertiesArray;
   BluetoothSignal signal(NS_LITERAL_STRING("RequestConfirmation"),
                          NS_LITERAL_STRING(KEY_LOCAL_AGENT), value);
   nsRefPtr<DistributeBluetoothSignalTask>
     t = new DistributeBluetoothSignalTask(signal);
   if (NS_FAILED(NS_DispatchToMainThread(t))) {
-    NS_WARNING("Failed to dispatch to main thread!");
+    BT_WARNING("Failed to dispatch to main thread!");
   }
 }
 
 static void
 BondStateChangedCallback(bt_status_t aStatus, bt_bdaddr_t* aRemoteBdAddress,
                          bt_bond_state_t aState)
 {
   MOZ_ASSERT(!NS_IsMainThread());
@@ -624,48 +632,41 @@ EnsureBluetoothHalLoad()
   int err = hw_get_module(BT_HARDWARE_MODULE_ID, (hw_module_t const**)&module);
   if (err != 0) {
     BT_LOGR("Error: %s ", strerror(err));
     return false;
   }
   module->methods->open(module, BT_HARDWARE_MODULE_ID, &device);
   sBtDevice = (bluetooth_device_t *)device;
   sBtInterface = sBtDevice->get_bluetooth_interface();
-  BT_LOGD("Bluetooth HAL loaded");
+
+  int ret = sBtInterface->init(&sBluetoothCallbacks);
+  if (ret != BT_STATUS_SUCCESS) {
+    BT_LOGR("Error while setting the callbacks %s", __FUNCTION__);
+    sBtInterface = nullptr;
+  }
 
   return true;
 }
 
 static nsresult
 StartStopGonkBluetooth(bool aShouldEnable)
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  static bool sIsBtInterfaceInitialized = false;
-
-  if (!EnsureBluetoothHalLoad()) {
-    BT_LOGR("Failed to load bluedroid library.\n");
-    return NS_ERROR_FAILURE;
-  }
-
-  if (sIsBtEnabled == aShouldEnable)
-    return NS_OK;
+  NS_ENSURE_TRUE(sBtInterface, NS_ERROR_FAILURE);
+  NS_ENSURE_TRUE(sIsBtEnabled != aShouldEnable, NS_OK);
 
-  if (sBtInterface && !sIsBtInterfaceInitialized) {
-    int ret = sBtInterface->init(&sBluetoothCallbacks);
-    if (ret != BT_STATUS_SUCCESS) {
-      BT_LOGR("Error while setting the callbacks %s", __FUNCTION__);
-      sBtInterface = nullptr;
-      return NS_ERROR_FAILURE;
-    }
-    sIsBtInterfaceInitialized = true;
-  }
   int ret = aShouldEnable ? sBtInterface->enable() : sBtInterface->disable();
+  NS_ENSURE_TRUE(ret == BT_STATUS_SUCCESS, NS_ERROR_FAILURE);
 
-  return (ret == BT_STATUS_SUCCESS) ? NS_OK : NS_ERROR_FAILURE;
+  MonitorAutoLock lock(*sToggleBtMonitor);
+  lock.Wait();
+
+  return NS_OK;
 }
 
 static void
 ReplyStatusError(BluetoothReplyRunnable* aBluetoothReplyRunnable,
                  int aStatusCode, const nsAString& aCustomMsg)
 {
   MOZ_ASSERT(aBluetoothReplyRunnable, "Reply runnable is nullptr");
 
@@ -690,16 +691,36 @@ ReplyStatusError(BluetoothReplyRunnable*
 
   DispatchBluetoothReply(aBluetoothReplyRunnable, BluetoothValue(true),
                          replyError);
 }
 
 /**
  *  Member functions
  */
+BluetoothServiceBluedroid::BluetoothServiceBluedroid()
+{
+  sToggleBtMonitor = new Monitor("BluetoothService.sToggleBtMonitor");
+
+  if (!EnsureBluetoothHalLoad()) {
+    BT_LOGR("Error! Failed to load bluedroid library.\n");
+    return;
+  }
+
+  // Register all the bluedroid callbacks before enable() get called
+  // It is required to register a2dp callbacks before a2dp media task starts up.
+  BluetoothHfpManager::Get();
+  BluetoothA2dpManager::Get();
+}
+
+BluetoothServiceBluedroid::~BluetoothServiceBluedroid()
+{
+  sToggleBtMonitor = nullptr;
+}
+
 nsresult
 BluetoothServiceBluedroid::StartInternal()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
   nsresult ret = StartStopGonkBluetooth(true);
   if (NS_FAILED(ret)) {
     BT_LOGR("Error: %s", __FUNCTION__);
@@ -721,21 +742,16 @@ BluetoothServiceBluedroid::StopInternal(
   return ret;
 }
 
 bool
 BluetoothServiceBluedroid::IsEnabledInternal()
 {
   MOZ_ASSERT(!NS_IsMainThread());
 
-  if (!EnsureBluetoothHalLoad()) {
-    NS_ERROR("Failed to load bluedroid library.\n");
-    return false;
-  }
-
   return sIsBtEnabled;
 }
 
 nsresult
 BluetoothServiceBluedroid::GetDefaultAdapterPathInternal(
   BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
@@ -1165,44 +1181,30 @@ ConnectDisconnect(bool aConnect, const n
 }
 
 void
 BluetoothServiceBluedroid::Connect(const nsAString& aDeviceAddress,
                                    uint32_t aCod,
                                    uint16_t aServiceUuid,
                                    BluetoothReplyRunnable* aRunnable)
 {
-  // TODO: Remove this error reply once Connect() is done in profile managers
-  if (aRunnable) {
-    DispatchBluetoothReply(aRunnable, BluetoothValue(),
-                           NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
-    return;
-  }
-
   ConnectDisconnect(true, aDeviceAddress, aRunnable, aServiceUuid, aCod);
 }
 
 bool
 BluetoothServiceBluedroid::IsConnected(uint16_t aProfileId)
 {
   return true;
 }
 
 void
 BluetoothServiceBluedroid::Disconnect(
   const nsAString& aDeviceAddress, uint16_t aServiceUuid,
   BluetoothReplyRunnable* aRunnable)
 {
-  // TODO: Remove this error reply once Disconnect() is done in profile managers
-  if (aRunnable) {
-    DispatchBluetoothReply(aRunnable, BluetoothValue(),
-                           NS_LITERAL_STRING(ERR_CONNECTION_FAILED));
-    return;
-  }
-
   ConnectDisconnect(false, aDeviceAddress, aRunnable, aServiceUuid);
 }
 
 void
 BluetoothServiceBluedroid::SendFile(const nsAString& aDeviceAddress,
                                     BlobParent* aBlobParent,
                                     BlobChild* aBlobChild,
                                     BluetoothReplyRunnable* aRunnable)
--- a/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.h
+++ b/dom/bluetooth/bluedroid/gonk/BluetoothServiceBluedroid.h
@@ -15,24 +15,28 @@
 typedef char bdstr_t[18];
 
 const bt_interface_t*
 GetBluetoothInterface();
 
 void
 StringToBdAddressType(const nsAString& aBdAddress,
                       bt_bdaddr_t *aRetBdAddressType);
-
-class DBusMessage;
+void
+BdAddressTypeToString(bt_bdaddr_t* aBdAddressType,
+                      nsAString& aRetBdAddress);
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothServiceBluedroid : public BluetoothService
 {
 public:
+  BluetoothServiceBluedroid();
+  ~BluetoothServiceBluedroid();
+
   virtual nsresult StartInternal();
   virtual nsresult StopInternal();
   virtual bool IsEnabledInternal();
 
   virtual nsresult GetDefaultAdapterPathInternal(
                                              BluetoothReplyRunnable* aRunnable);
 
   virtual nsresult GetConnectedDevicePropertiesInternal(uint16_t aProfileId,
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothA2dpManager.cpp
@@ -0,0 +1,452 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "base/basictypes.h"
+
+#include "BluetoothA2dpManager.h"
+
+#include "BluetoothCommon.h"
+#include "BluetoothService.h"
+#include "BluetoothSocket.h"
+#include "BluetoothUtils.h"
+
+#include "mozilla/dom/bluetooth/BluetoothTypes.h"
+#include "mozilla/Services.h"
+#include "mozilla/StaticPtr.h"
+#include "nsIObserverService.h"
+#include "MainThreadUtils.h"
+
+
+using namespace mozilla;
+USING_BLUETOOTH_NAMESPACE
+
+namespace {
+  StaticRefPtr<BluetoothA2dpManager> sBluetoothA2dpManager;
+  bool sInShutdown = false;
+} // anonymous namespace
+
+NS_IMETHODIMP
+BluetoothA2dpManager::Observe(nsISupports* aSubject,
+                              const char* aTopic,
+                              const PRUnichar* aData)
+{
+  MOZ_ASSERT(sBluetoothA2dpManager);
+
+  if (!strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) {
+    HandleShutdown();
+    return NS_OK;
+  }
+
+  MOZ_ASSERT(false, "BluetoothA2dpManager got unexpected topic!");
+  return NS_ERROR_UNEXPECTED;
+}
+
+BluetoothA2dpManager::BluetoothA2dpManager()
+{
+  ResetA2dp();
+  ResetAvrcp();
+}
+
+bool
+BluetoothA2dpManager::Init()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE(obs, false);
+  if (NS_FAILED(obs->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, false))) {
+    BT_WARNING("Failed to add shutdown observer!");
+    return false;
+  }
+
+  return true;
+}
+
+BluetoothA2dpManager::~BluetoothA2dpManager()
+{
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obs);
+  if (NS_FAILED(obs->RemoveObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID))) {
+    BT_WARNING("Failed to remove shutdown observer!");
+  }
+}
+
+void
+BluetoothA2dpManager::ResetA2dp()
+{
+  mA2dpConnected = false;
+  mSinkState = SinkState::SINK_DISCONNECTED;
+  mController = nullptr;
+}
+
+void
+BluetoothA2dpManager::ResetAvrcp()
+{
+  mAvrcpConnected = false;
+  mDuration = 0;
+  mMediaNumber = 0;
+  mTotalMediaCount = 0;
+  mPosition = 0;
+  mPlayStatus = ControlPlayStatus::PLAYSTATUS_UNKNOWN;
+}
+
+static BluetoothA2dpManager::SinkState
+StatusStringToSinkState(const nsAString& aStatus)
+{
+  BluetoothA2dpManager::SinkState state =
+    BluetoothA2dpManager::SinkState::SINK_UNKNOWN;
+  if (aStatus.EqualsLiteral("disconnected")) {
+    state = BluetoothA2dpManager::SinkState::SINK_DISCONNECTED;
+  } else if (aStatus.EqualsLiteral("connecting")) {
+    state = BluetoothA2dpManager::SinkState::SINK_CONNECTING;
+  } else if (aStatus.EqualsLiteral("connected")) {
+    state = BluetoothA2dpManager::SinkState::SINK_CONNECTED;
+  } else if (aStatus.EqualsLiteral("playing")) {
+    state = BluetoothA2dpManager::SinkState::SINK_PLAYING;
+  } else {
+    BT_WARNING("Unknown sink state");
+  }
+  return state;
+}
+
+//static
+BluetoothA2dpManager*
+BluetoothA2dpManager::Get()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // If sBluetoothA2dpManager already exists, exit early
+  if (sBluetoothA2dpManager) {
+    return sBluetoothA2dpManager;
+  }
+
+  // If we're in shutdown, don't create a new instance
+  NS_ENSURE_FALSE(sInShutdown, nullptr);
+
+  // Create a new instance, register, and return
+  BluetoothA2dpManager* manager = new BluetoothA2dpManager();
+  NS_ENSURE_TRUE(manager->Init(), nullptr);
+
+  sBluetoothA2dpManager = manager;
+  return sBluetoothA2dpManager;
+}
+
+void
+BluetoothA2dpManager::HandleShutdown()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  sInShutdown = true;
+  Disconnect(nullptr);
+  sBluetoothA2dpManager = nullptr;
+}
+
+void
+BluetoothA2dpManager::Connect(const nsAString& aDeviceAddress,
+                              BluetoothProfileController* aController)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(!aDeviceAddress.IsEmpty());
+  MOZ_ASSERT(aController && !mController);
+
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || sInShutdown) {
+    aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    return;
+  }
+
+  if (mA2dpConnected) {
+    aController->OnConnect(NS_LITERAL_STRING(ERR_ALREADY_CONNECTED));
+    return;
+  }
+
+  mDeviceAddress = aDeviceAddress;
+  mController = aController;
+
+  if (NS_FAILED(bs->SendSinkMessage(aDeviceAddress,
+                                    NS_LITERAL_STRING("Connect")))) {
+    aController->OnConnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    return;
+  }
+}
+
+void
+BluetoothA2dpManager::Disconnect(BluetoothProfileController* aController)
+{
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs) {
+    if (aController) {
+      aController->OnDisconnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    }
+    return;
+  }
+
+  if (!mA2dpConnected) {
+    if (aController) {
+      aController->OnDisconnect(NS_LITERAL_STRING(ERR_ALREADY_DISCONNECTED));
+    }
+    return;
+  }
+
+  MOZ_ASSERT(!mDeviceAddress.IsEmpty());
+  MOZ_ASSERT(!mController);
+
+  mController = aController;
+
+  if (NS_FAILED(bs->SendSinkMessage(mDeviceAddress,
+                                    NS_LITERAL_STRING("Disconnect")))) {
+    aController->OnDisconnect(NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    return;
+  }
+}
+
+void
+BluetoothA2dpManager::OnConnect(const nsAString& aErrorStr)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * On the one hand, notify the controller that we've done for outbound
+   * connections. On the other hand, we do nothing for inbound connections.
+   */
+  NS_ENSURE_TRUE_VOID(mController);
+
+  nsRefPtr<BluetoothProfileController> controller = mController.forget();
+  controller->OnConnect(aErrorStr);
+}
+
+void
+BluetoothA2dpManager::OnDisconnect(const nsAString& aErrorStr)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  /**
+   * On the one hand, notify the controller that we've done for outbound
+   * connections. On the other hand, we do nothing for inbound connections.
+   */
+  NS_ENSURE_TRUE_VOID(mController);
+
+  nsRefPtr<BluetoothProfileController> controller = mController.forget();
+  controller->OnDisconnect(aErrorStr);
+}
+
+/* HandleSinkPropertyChanged update sink state in A2dp
+ *
+ * Possible values: "disconnected", "connecting", "connected", "playing"
+ *
+ * 1. "disconnected" -> "connecting"
+ *    Either an incoming or outgoing connection attempt ongoing
+ * 2. "connecting" -> "disconnected"
+ *    Connection attempt failed
+ * 3. "connecting" -> "connected"
+ *    Successfully connected
+ * 4. "connected" -> "playing"
+ *    Audio stream active
+ * 5. "playing" -> "connected"
+ *    Audio stream suspended
+ * 6. "connected" -> "disconnected"
+ *    "playing" -> "disconnected"
+ *    Disconnected from local or the remote device
+ */
+void
+BluetoothA2dpManager::HandleSinkPropertyChanged(const BluetoothSignal& aSignal)
+{
+  MOZ_ASSERT(NS_IsMainThread());
+  MOZ_ASSERT(aSignal.value().type() == BluetoothValue::TArrayOfBluetoothNamedValue);
+
+  const nsString& address = aSignal.path();
+  const InfallibleTArray<BluetoothNamedValue>& arr =
+    aSignal.value().get_ArrayOfBluetoothNamedValue();
+  MOZ_ASSERT(arr.Length() == 1);
+
+  /**
+   * There are three properties:
+   * - "State": a string
+   * - "Connected": a boolean value
+   * - "Playing": a boolean value
+   *
+   * Note that only "State" is handled in this function.
+   */
+
+  const nsString& name = arr[0].name();
+  NS_ENSURE_TRUE_VOID(name.EqualsLiteral("State"));
+
+  const BluetoothValue& value = arr[0].value();
+  MOZ_ASSERT(value.type() == BluetoothValue::TnsString);
+  SinkState newState = StatusStringToSinkState(value.get_nsString());
+  NS_ENSURE_TRUE_VOID((newState != SinkState::SINK_UNKNOWN) &&
+                      (newState != mSinkState));
+
+  SinkState prevState = mSinkState;
+  mSinkState = newState;
+
+  switch(mSinkState) {
+    case SinkState::SINK_CONNECTING:
+      // case 1: Either an incoming or outgoing connection attempt ongoing
+      MOZ_ASSERT(prevState == SinkState::SINK_DISCONNECTED);
+      break;
+    case SinkState::SINK_PLAYING:
+      // case 4: Audio stream active
+      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED);
+      break;
+    case SinkState::SINK_CONNECTED:
+      // case 5: Audio stream suspended
+      if (prevState == SinkState::SINK_PLAYING) {
+        break;
+      }
+      
+      // case 3: Successfully connected
+      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTING);
+
+      mA2dpConnected = true;
+      mDeviceAddress = address;
+      NotifyConnectionStatusChanged();
+
+      OnConnect(EmptyString());
+      break;
+    case SinkState::SINK_DISCONNECTED:
+      // case 2: Connection attempt failed
+      if (prevState == SinkState::SINK_CONNECTING) {
+        OnConnect(NS_LITERAL_STRING("A2dpConnectionError"));
+        break;
+      }
+
+      // case 6: Disconnected from the remote device
+      MOZ_ASSERT(prevState == SinkState::SINK_CONNECTED ||
+                 prevState == SinkState::SINK_PLAYING) ;
+
+      mA2dpConnected = false;
+      NotifyConnectionStatusChanged();
+      mDeviceAddress.Truncate();
+      OnDisconnect(EmptyString());
+      break;
+    default:
+      break;
+  }
+}
+
+void
+BluetoothA2dpManager::NotifyConnectionStatusChanged()
+{
+  MOZ_ASSERT(NS_IsMainThread());
+
+  // Notify Gecko observers
+  nsCOMPtr<nsIObserverService> obs = services::GetObserverService();
+  NS_ENSURE_TRUE_VOID(obs);
+
+  if (NS_FAILED(obs->NotifyObservers(this,
+                                     BLUETOOTH_A2DP_STATUS_CHANGED_ID,
+                                     mDeviceAddress.get()))) {
+    BT_WARNING("Failed to notify bluetooth-a2dp-status-changed observsers!");
+  }
+
+  // Dispatch an event of status change
+  DispatchStatusChangedEvent(
+    NS_LITERAL_STRING(A2DP_STATUS_CHANGED_ID), mDeviceAddress, mA2dpConnected);
+}
+
+void
+BluetoothA2dpManager::OnGetServiceChannel(const nsAString& aDeviceAddress,
+                                          const nsAString& aServiceUuid,
+                                          int aChannel)
+{
+}
+
+void
+BluetoothA2dpManager::OnUpdateSdpRecords(const nsAString& aDeviceAddress)
+{
+}
+
+void
+BluetoothA2dpManager::GetAddress(nsAString& aDeviceAddress)
+{
+  aDeviceAddress = mDeviceAddress;
+}
+
+bool
+BluetoothA2dpManager::IsConnected()
+{
+  return mA2dpConnected;
+}
+
+void
+BluetoothA2dpManager::SetAvrcpConnected(bool aConnected)
+{
+  mAvrcpConnected = aConnected;
+  if (!aConnected) {
+    ResetAvrcp();
+  }
+}
+
+bool
+BluetoothA2dpManager::IsAvrcpConnected()
+{
+  return mAvrcpConnected;
+}
+
+void
+BluetoothA2dpManager::UpdateMetaData(const nsAString& aTitle,
+                                     const nsAString& aArtist,
+                                     const nsAString& aAlbum,
+                                     uint32_t aMediaNumber,
+                                     uint32_t aTotalMediaCount,
+                                     uint32_t aDuration)
+{
+  mTitle.Assign(aTitle);
+  mArtist.Assign(aArtist);
+  mAlbum.Assign(aAlbum);
+  mMediaNumber = aMediaNumber;
+  mTotalMediaCount = aTotalMediaCount;
+  mDuration = aDuration;
+}
+
+void
+BluetoothA2dpManager::UpdatePlayStatus(uint32_t aDuration,
+                                       uint32_t aPosition,
+                                       ControlPlayStatus aPlayStatus)
+{
+  mDuration = aDuration;
+  mPosition = aPosition;
+  mPlayStatus = aPlayStatus;
+}
+
+void
+BluetoothA2dpManager::GetAlbum(nsAString& aAlbum)
+{
+    aAlbum.Assign(mAlbum);
+}
+
+uint32_t
+BluetoothA2dpManager::GetDuration()
+{
+  return mDuration;
+}
+
+ControlPlayStatus
+BluetoothA2dpManager::GetPlayStatus()
+{
+  return mPlayStatus;
+}
+
+uint32_t
+BluetoothA2dpManager::GetPosition()
+{
+  return mPosition;
+}
+
+uint32_t
+BluetoothA2dpManager::GetMediaNumber()
+{
+  return mMediaNumber;
+}
+
+void
+BluetoothA2dpManager::GetTitle(nsAString& aTitle)
+{
+  aTitle.Assign(mTitle);
+}
+
+NS_IMPL_ISUPPORTS1(BluetoothA2dpManager, nsIObserver)
+
new file mode 100644
--- /dev/null
+++ b/dom/bluetooth/bluez/BluetoothA2dpManager.h
@@ -0,0 +1,90 @@
+/* -*- Mode: c++; c-basic-offset: 2; indent-tabs-mode: nil; tab-width: 40 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_bluetooth_bluetootha2dpmanager_h__
+#define mozilla_dom_bluetooth_bluetootha2dpmanager_h__
+
+#include "BluetoothCommon.h"
+#include "BluetoothProfileController.h"
+#include "BluetoothProfileManagerBase.h"
+
+BEGIN_BLUETOOTH_NAMESPACE
+
+class BluetoothA2dpManager : public BluetoothProfileManagerBase
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+  BT_DECL_PROFILE_MGR_BASE
+  virtual void GetName(nsACString& aName)
+  {
+    aName.AssignLiteral("A2DP");
+  }
+
+  enum SinkState {
+    SINK_UNKNOWN,
+    SINK_DISCONNECTED,
+    SINK_CONNECTING,
+    SINK_CONNECTED,
+    SINK_PLAYING,
+  };
+
+  static BluetoothA2dpManager* Get();
+  ~BluetoothA2dpManager();
+  void ResetA2dp();
+  void ResetAvrcp();
+
+  // A2DP-specific functions
+  void HandleSinkPropertyChanged(const BluetoothSignal& aSignal);
+
+  // AVRCP-specific functions
+  void SetAvrcpConnected(bool aConnected);
+  bool IsAvrcpConnected();
+  void UpdateMetaData(const nsAString& aTitle,
+                      const nsAString& aArtist,
+                      const nsAString& aAlbum,
+                      uint32_t aMediaNumber,
+                      uint32_t aTotalMediaCount,
+                      uint32_t aDuration);
+  void UpdatePlayStatus(uint32_t aDuration,
+                        uint32_t aPosition,
+                        ControlPlayStatus aPlayStatus);
+  void GetAlbum(nsAString& aAlbum);
+  uint32_t GetDuration();
+  ControlPlayStatus GetPlayStatus();
+  uint32_t GetPosition();
+  uint32_t GetMediaNumber();
+  void GetTitle(nsAString& aTitle);
+
+private:
+  BluetoothA2dpManager();
+  bool Init();
+
+  void HandleShutdown();
+  void NotifyConnectionStatusChanged();
+
+  nsString mDeviceAddress;
+  nsRefPtr<BluetoothProfileController> mController;
+
+  // A2DP data member
+  bool mA2dpConnected;
+  SinkState mSinkState;
+
+  // AVRCP data member
+  bool mAvrcpConnected;
+  nsString mAlbum;
+  nsString mArtist;
+  nsString mTitle;
+  uint32_t mDuration;
+  uint32_t mMediaNumber;
+  uint32_t mTotalMediaCount;
+  uint32_t mPosition;
+  ControlPlayStatus mPlayStatus;
+};
+
+END_BLUETOOTH_NAMESPACE
+
+#endif
--- a/dom/bluetooth/moz.build
+++ b/dom/bluetooth/moz.build
@@ -2,19 +2,17 @@
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 if CONFIG['MOZ_B2G_BT']:
     PARALLEL_DIRS += ['interfaces']
 
-
     SOURCES += [
-        'BluetoothA2dpManager.cpp',
         'BluetoothAdapter.cpp',
         'BluetoothDevice.cpp',
         'BluetoothHidManager.cpp',
         'BluetoothManager.cpp',
         'BluetoothOppManager.cpp',
         'BluetoothProfileController.cpp',
         'BluetoothPropertyContainer.cpp',
         'BluetoothReplyRunnable.cpp',
@@ -32,27 +30,29 @@ if CONFIG['MOZ_B2G_BT']:
     if CONFIG['MOZ_B2G_RIL']:
         SOURCES += [
             'BluetoothRilListener.cpp',
         ]
 
     if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'gonk':
         if CONFIG['MOZ_B2G_BT_BLUEZ']:
             SOURCES += [
+                'bluez/BluetoothA2dpManager.cpp',
                 'bluez/BluetoothHfpManager.cpp',
                 'bluez/gonk/BluetoothGonkService.cpp',
                 'bluez/linux/BluetoothDBusService.cpp',
             ]
             LOCAL_INCLUDES += [
                 'bluez',
                 'bluez/gonk',
                 'bluez/linux',
             ]
         elif CONFIG['MOZ_B2G_BT_BLUEDROID']:
             SOURCES += [
+                'bluedroid/BluetoothA2dpManager.cpp',
                 'bluedroid/BluetoothHfpManager.cpp',
                 'bluedroid/gonk/BluetoothServiceBluedroid.cpp',
             ]
             LOCAL_INCLUDES += [
                 'bluedroid',
                 'bluedroid/gonk',
             ]
     elif CONFIG['MOZ_ENABLE_DBUS']:
--- a/dom/contacts/ContactManager.js
+++ b/dom/contacts/ContactManager.js
@@ -806,14 +806,15 @@ ContactManager.prototype = {
     }.bind(this);
 
     this.askPermission("listen", null, allowCallback);
   },
 
   classID: Components.ID("{8beb3a66-d70a-4111-b216-b8e995ad3aff}"),
   contractID: "@mozilla.org/contactManager;1",
   QueryInterface: XPCOMUtils.generateQI([Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver,
                                          Ci.nsIDOMGlobalPropertyInitializer]),
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([
   Contact, ContactManager, ContactFieldImpl, ContactAddressImpl, ContactTelFieldImpl
 ]);
--- a/dom/icc/interfaces/nsIDOMIccManager.idl
+++ b/dom/icc/interfaces/nsIDOMIccManager.idl
@@ -1,38 +1,35 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #include "nsIDOMEventTarget.idl"
-#include "SimToolKit.idl"
 
-interface nsIDOMDOMRequest;
-interface nsIDOMEventListener;
-interface nsIDOMMozIccInfo;
+interface nsIDOMMozIcc;
 
-[scriptable, builtinclass, uuid(50782fe0-4185-4471-a374-e362b73febdb)]
+[scriptable, builtinclass, uuid(23067d6f-e0cb-4f34-8648-77c2b25a11f5)]
 interface nsIDOMMozIccManager : nsIDOMEventTarget
 {
   /**
-   * STK Menu Presentation types.
+   * STK menu presentation types.
    */
   const unsigned short STK_MENU_TYPE_NOT_SPECIFIED      = 0x00;
   const unsigned short STK_MENU_TYPE_DATA_VALUES        = 0x01;
   const unsigned short STK_MENU_TYPE_NAVIGATION_OPTIONS = 0x03;
 
   /**
    * Browser launch mode.
    */
   const unsigned short STK_BROWSER_MODE_LAUNCH_IF_NOT_ALREADY_LAUNCHED = 0x00;
   const unsigned short STK_BROWSER_MODE_USING_EXISTING_BROWSER         = 0x02;
   const unsigned short STK_BROWSER_MODE_USING_NEW_BROWSER              = 0x03;
 
   /**
-   * STK Proactive commands.
+   * STK proactive commands.
    *
    * @see TS 11.14, clause 13.4
    */
   const unsigned short STK_CMD_REFRESH               = 0x01;
   const unsigned short STK_CMD_POLL_INTERVAL         = 0x03;
   const unsigned short STK_CMD_POLL_OFF              = 0x04;
   const unsigned short STK_CMD_SET_UP_EVENT_LIST     = 0x05;
   const unsigned short STK_CMD_SET_UP_CALL           = 0x10;
@@ -51,17 +48,17 @@ interface nsIDOMMozIccManager : nsIDOMEv
   const unsigned short STK_CMD_TIMER_MANAGEMENT      = 0x27;
   const unsigned short STK_CMD_SET_UP_IDLE_MODE_TEXT = 0x28;
   const unsigned short STK_CMD_OPEN_CHANNEL          = 0x30;
   const unsigned short STK_CMD_CLOSE_CHANNEL         = 0x31;
   const unsigned short STK_CMD_RECEIVE_DATA          = 0x32;
   const unsigned short STK_CMD_SEND_DATA             = 0x33;
 
   /**
-   * STK Result code.
+   * STK result code.
    *
    * @see TS 11.14, clause 12.12
    *
    * Results '0X' and '1X' indicate that the command has been performed.
    */
   /** Command performed successfully */
   const unsigned short STK_RESULT_OK                                = 0x00;
 
@@ -123,67 +120,67 @@ interface nsIDOMMozIccManager : nsIDOMEv
   const unsigned short STK_RESULT_CMD_TYPE_NOT_UNDERSTOOD           = 0x31;
 
   /** Command data not understood by terminal */
   const unsigned short STK_RESULT_CMD_DATA_NOT_UNDERSTOOD           = 0x32;
 
   /** Command number not known by terminal */
   const unsigned short STK_RESULT_CMD_NUM_NOT_KNOWN                 = 0x33;
 
-  /** SS Return Error */
+  /** SS return error */
   const unsigned short STK_RESULT_SS_RETURN_ERROR                   = 0x34;
 
   /** SMS RP-ERROR */
   const unsigned short STK_RESULT_SMS_RP_ERROR                      = 0x35;
 
   /** Error, required values are missing */
   const unsigned short STK_RESULT_REQUIRED_VALUES_MISSING           = 0x36;
 
-  /** USSD Return Error */
+  /** USSD return error */
   const unsigned short STK_RESULT_USSD_RETURN_ERROR                 = 0x37;
 
   /** MultipleCard commands error */
   const unsigned short STK_RESULT_MULTI_CARDS_CMD_ERROR             = 0x38;
 
   /**
    * Interaction with call control by USIM or MO short message control by
-   * USIM, permanent problem
+   * USIM, permanent problem.
    */
   const unsigned short STK_RESULT_USIM_CALL_CONTROL_PERMANENT       = 0x39;
 
-  /** Bearer Independent Protocol error */
+  /** Bearer independent protocol error */
   const unsigned short STK_RESULT_BIP_ERROR                         = 0x3a;
 
   /**
-   * STK Event List
+   * STK event list.
    */
-   const unsigned short STK_EVENT_TYPE_MT_CALL                          = 0x00;
-   const unsigned short STK_EVENT_TYPE_CALL_CONNECTED                   = 0x01;
-   const unsigned short STK_EVENT_TYPE_CALL_DISCONNECTED                = 0x02;
-   const unsigned short STK_EVENT_TYPE_LOCATION_STATUS                  = 0x03;
-   const unsigned short STK_EVENT_TYPE_USER_ACTIVITY                    = 0x04;
-   const unsigned short STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE            = 0x05;
-   const unsigned short STK_EVENT_TYPE_CARD_READER_STATUS               = 0x06;
-   const unsigned short STK_EVENT_TYPE_LANGUAGE_SELECTION               = 0x07;
-   const unsigned short STK_EVENT_TYPE_BROWSER_TERMINATION              = 0x08;
-   const unsigned short STK_EVENT_TYPE_DATA_AVAILABLE                   = 0x09;
-   const unsigned short STK_EVENT_TYPE_CHANNEL_STATUS                   = 0x0a;
-   const unsigned short STK_EVENT_TYPE_SINGLE_ACCESS_TECHNOLOGY_CHANGED = 0x0b;
-   const unsigned short STK_EVENT_TYPE_DISPLAY_PARAMETER_CHANGED        = 0x0c;
-   const unsigned short STK_EVENT_TYPE_LOCAL_CONNECTION                 = 0x0d;
-   const unsigned short STK_EVENT_TYPE_NETWORK_SEARCH_MODE_CHANGED      = 0x0e;
-   const unsigned short STK_EVENT_TYPE_BROWSING_STATUS                  = 0x0f;
-   const unsigned short STK_EVENT_TYPE_FRAMES_INFORMATION_CHANGED       = 0x10;
+  const unsigned short STK_EVENT_TYPE_MT_CALL                          = 0x00;
+  const unsigned short STK_EVENT_TYPE_CALL_CONNECTED                   = 0x01;
+  const unsigned short STK_EVENT_TYPE_CALL_DISCONNECTED                = 0x02;
+  const unsigned short STK_EVENT_TYPE_LOCATION_STATUS                  = 0x03;
+  const unsigned short STK_EVENT_TYPE_USER_ACTIVITY                    = 0x04;
+  const unsigned short STK_EVENT_TYPE_IDLE_SCREEN_AVAILABLE            = 0x05;
+  const unsigned short STK_EVENT_TYPE_CARD_READER_STATUS               = 0x06;
+  const unsigned short STK_EVENT_TYPE_LANGUAGE_SELECTION               = 0x07;
+  const unsigned short STK_EVENT_TYPE_BROWSER_TERMINATION              = 0x08;
+  const unsigned short STK_EVENT_TYPE_DATA_AVAILABLE                   = 0x09;
+  const unsigned short STK_EVENT_TYPE_CHANNEL_STATUS                   = 0x0a;
+  const unsigned short STK_EVENT_TYPE_SINGLE_ACCESS_TECHNOLOGY_CHANGED = 0x0b;
+  const unsigned short STK_EVENT_TYPE_DISPLAY_PARAMETER_CHANGED        = 0x0c;
+  const unsigned short STK_EVENT_TYPE_LOCAL_CONNECTION                 = 0x0d;
+  const unsigned short STK_EVENT_TYPE_NETWORK_SEARCH_MODE_CHANGED      = 0x0e;
+  const unsigned short STK_EVENT_TYPE_BROWSING_STATUS                  = 0x0f;
+  const unsigned short STK_EVENT_TYPE_FRAMES_INFORMATION_CHANGED       = 0x10;
 
-   /**
-    * The service state of STK Location Status.
-    */
-   const unsigned short STK_SERVICE_STATE_NORMAL      = 0x00;
-   const unsigned short STK_SERVICE_STATE_LIMITED     = 0x01;
-   const unsigned short STK_SERVICE_STATE_UNAVAILABLE = 0x02;
+  /**
+   * The service state of STK location status.
+   */
+  const unsigned short STK_SERVICE_STATE_NORMAL      = 0x00;
+  const unsigned short STK_SERVICE_STATE_LIMITED     = 0x01;
+  const unsigned short STK_SERVICE_STATE_UNAVAILABLE = 0x02;
 
   /**
    * Tone type.
    */
   const unsigned short STK_TONE_TYPE_DIAL_TONE                = 0x01;
   const unsigned short STK_TONE_TYPE_CALLED_SUBSCRIBER_BUSY   = 0x02;
   const unsigned short STK_TONE_TYPE_CONGESTION               = 0x03;
   const unsigned short STK_TONE_TYPE_RADIO_PATH_ACK           = 0x04;
@@ -191,366 +188,62 @@ interface nsIDOMMozIccManager : nsIDOMEv
   const unsigned short STK_TONE_TYPE_ERROR                    = 0x06;
   const unsigned short STK_TONE_TYPE_CALL_WAITING_TONE        = 0x07;
   const unsigned short STK_TONE_TYPE_RINGING_TONE             = 0x08;
   const unsigned short STK_TONE_TYPE_GENERAL_BEEP             = 0x10;
   const unsigned short STK_TONE_TYPE_POSITIVE_ACK_TONE        = 0x11;
   const unsigned short STK_TONE_TYPE_NEGATIVE_ACK_TONE        = 0x12;
 
   /**
-   * Time unit
+   * Time unit.
    */
   const unsigned short STK_TIME_UNIT_MINUTE       = 0x00;
   const unsigned short STK_TIME_UNIT_SECOND       = 0x01;
   const unsigned short STK_TIME_UNIT_TENTH_SECOND = 0x02;
 
   /**
-   * Local Information list
+   * Local Information list.
    *
    * @see TS 102.223, clause 8.6
    */
   const unsigned short STK_LOCAL_INFO_LOCATION_INFO  = 0x00;
   const unsigned short STK_LOCAL_INFO_IMEI           = 0x01;
   const unsigned short STK_LOCAL_INFO_DATE_TIME_ZONE = 0x03;
   const unsigned short STK_LOCAL_INFO_LANGUAGE       = 0x04;
 
-   /**
-   * Timer Management
+  /**
+   * Timer management.
    */
   const unsigned short STK_TIMER_START             = 0x00;
   const unsigned short STK_TIMER_DEACTIVATE        = 0x01;
   const unsigned short STK_TIMER_GET_CURRENT_VALUE = 0x02;
 
   /**
-   * Browser Termination Cause
+   * Browser termination cause.
    */
   const unsigned short STK_BROWSER_TERMINATION_CAUSE_USER  = 0x00;
   const unsigned short STK_BROWSER_TERMINATION_CAUSE_ERROR = 0x01;
 
   /**
-   * Send the response back to ICC after an attempt to execute STK Proactive
-   * Command.
-   *
-   * @param command
-   *        Command received from ICC. See MozStkCommand.
-   * @param response
-   *        The response that will be sent to ICC.
-   * @see MozStkResponse for the detail of response.
-   */
-  void sendStkResponse(in jsval command, in jsval response);
-
-  /**
-   * Send the "Menu Selection" Envelope command to ICC for menu selection.
-   *
-   * @param itemIdentifier
-   *        The identifier of the item selected by user.
-   * @param helpRequested
-   *        true if user requests to provide help information, false otherwise.
-   */
-  void sendStkMenuSelection(in unsigned short itemIdentifier,
-                            in boolean        helpRequested);
-
-  /**
-   * Send the "Timer Expiration" Envelope command to ICC for TIMER MANAGEMENT.
-   *
-   * @param timer
-   *        The identifier and value for a timer.
-   *        timerId: Identifier of the timer that has expired.
-   *        timerValue: Different between the time when this command is issued
-   *                    and when the timer was initially started.
-   *        @see MozStkTimer
-   */
-  void sendStkTimerExpiration(in jsval timer);
-
-  /**
-   * Send "Event Download" Envelope command to ICC.
-   * ICC will not respond with any data for this command.
-   *
-   * @param event
-   *        one of events below:
-   *        - MozStkLocationEvent
-   *        - MozStkCallEvent
-   *        - MozStkLanguageSelectionEvent
-   *        - MozStkGeneralEvent
-   *        - MozStkBrowserTerminationEvent
-   */
-  void sendStkEventDownload(in jsval event);
-
-  /**
-   * The 'stkcommand' event is notified whenever STK Proactive Command is
-   * issued from ICC.
+   * Array of iccIds that are currently detected.
    */
-  [implicit_jscontext] attribute jsval onstkcommand;
-
-  /**
-   * 'stksessionend' event is notified whenever STK Session is terminated by
-   * ICC.
-   */
-  [implicit_jscontext] attribute jsval onstksessionend;
-
-  // UICC Card Information.
-
-  /**
-   * Information stored in the device's ICC card.
-   *
-   * Null if the card is not detected.
-   */
-  readonly attribute nsIDOMMozIccInfo iccInfo;
-
-  /**
-   * The 'iccinfochange' event is notified whenever the icc info object
-   * changes.
-   */
-  [implicit_jscontext] attribute jsval oniccinfochange;
-
-  // UICC Card State.
-
-  /**
-   * Indicates the state of the device's ICC card.
-   *
-   * Possible values: null, 'illegal', 'unknown', 'absent', 'pinRequired',
-   * 'pukRequired', 'personalizationInProgress', 'networkLocked',
-   * 'corporateLocked', 'serviceProviderLocked', 'networkPukRequired',
-   * 'corporatePukRequired', 'serviceProviderPukRequired',
-   * 'personalizationReady', 'ready', 'permanentBlocked'.
-   */
-  readonly attribute DOMString cardState;
-
-  /**
-   * The 'cardstatechange' event is notified when the 'cardState' attribute
-   * changes value.
-   */
-  [implicit_jscontext] attribute jsval oncardstatechange;
-
-  // UICC Card Lock interfaces.
-
-  /**
-   * Find out about the status of an ICC lock (e.g. the PIN lock).
-   *
-   * @param lockType
-   *        Identifies the lock type, e.g. "pin" for the PIN lock, "fdn" for
-   *        the FDN lock.
-   *
-   * @return a DOM Request.
-   *         The request's result will be an object containing
-   *         information about the specified lock's status,
-   *         e.g. {lockType: "pin", enabled: true}.
-   */
-  nsIDOMDOMRequest getCardLock(in DOMString lockType);
+  readonly attribute jsval iccIds;  // DOMString[]
 
   /**
-   * Unlock a card lock.
-   *
-   * @param info
-   *        An object containing the information necessary to unlock
-   *        the given lock. At a minimum, this object must have a
-   *        "lockType" attribute which specifies the type of lock, e.g.
-   *        "pin" for the PIN lock. Other attributes are dependent on
-   *        the lock type.
-   *
-   * Examples:
-   *
-   * (1) Unlocking the PIN:
-   *
-   *   unlockCardLock({lockType: "pin",
-   *                   pin: "..."});
-   *
-   * (2) Unlocking the PUK and supplying a new PIN:
-   *
-   *   unlockCardLock({lockType: "puk",
-   *                   puk: "...",
-   *                   newPin: "..."});
-   *
-   * (3) Network depersonalization. Unlocking the network control key (NCK).
-   *
-   *   unlockCardLock({lockType: "nck",
-   *                   pin: "..."});
-   *
-   * (4) Corporate depersonalization. Unlocking the corporate control key (CCK).
-   *
-   *   unlockCardLock({lockType: "cck",
-   *                   pin: "..."});
-   *
-   * (5) Service Provider depersonalization. Unlocking the service provider
-   *     control key (SPCK).
-   *
-   *   unlockCardLock({lockType: "spck",
-   *                   pin: "..."});
+   * Get ICC object by iccId.
    *
-   * (6) Network PUK depersonalization. Unlocking the network control key (NCK).
-   *
-   *   unlockCardLock({lockType: "nckPuk",
-   *                   puk: "..."});
-   *
-   * (7) Corporate PUK depersonalization. Unlocking the corporate control key
-   *     (CCK).
-   *
-   *   unlockCardLock({lockType: "cckPuk",
-   *                   puk: "..."});
-   *
-   * (8) Service Provider PUK depersonalization. Unlocking the service provider
-   *     control key (SPCK).
-   *
-   *   unlockCardLock({lockType: "spckPuk",
-   *                   puk: "..."});
+   * @param iccId
+   *        The identifier of the ICC.
    *
-   * @return a nsIDOMDOMRequest.
-   *         The request's result will be an object containing
-   *         information about the unlock operation.
-   *
-   * Examples:
-   *
-   * (1) Unlocking failed:
-   *
-   *     {
-   *       lockType:   "pin",
-   *       success:    false,
-   *       retryCount: 2
-   *     }
-   *
-   * (2) Unlocking succeeded:
-   *
-   *     {
-   *       lockType:  "pin",
-   *       success:   true
-   *     }
+   * @return see MozIcc.webidl for the detail.
    */
-  nsIDOMDOMRequest unlockCardLock(in jsval info);
+  nsISupports getIccById(in DOMString iccId);
 
   /**
-   * Modify the state of a card lock.
-   *
-   * @param info
-   *        An object containing information about the lock and
-   *        how to modify its state. At a minimum, this object
-   *        must have a "lockType" attribute which specifies the
-   *        type of lock, e.g. "pin" for the PIN lock. Other
-   *        attributes are dependent on the lock type.
-   *
-   * Examples:
-   *
-   * (1a) Disabling the PIN lock:
-   *
-   *   setCardLock({lockType: "pin",
-   *                pin: "...",
-   *                enabled: false});
-   *
-   * (1b) Disabling the FDN lock:
-   *
-   *   setCardLock({lockType: "fdn",
-   *                pin2: "...",
-   *                enabled: false});
-   *
-   * (2) Changing the PIN:
-   *
-   *   setCardLock({lockType: "pin",
-   *                pin: "...",
-   *                newPin: "..."});
-   *
-   * @return a nsIDOMDOMRequest.
-   *         The request's result will be an object containing
-   *         information about the operation.
-   *
-   * Examples:
-   *
-   * (1) Enabling/Disabling card lock failed or change card lock failed.
-   *
-   *     {
-   *       lockType: "pin",
-   *       success: false,
-   *       retryCount: 2
-   *     }
-   *
-   * (2) Enabling/Disabling card lock succeed or change card lock succeed.
-   *
-   *     {
-   *       lockType: "pin",
-   *       success: true
-   *     }
+   * 'oniccdetected' event is notified whenever a new ICC is detected.
    */
-  nsIDOMDOMRequest setCardLock(in jsval info);
-
-  /**
-   * Retrieve the number of remaining tries for unlocking the card.
-   *
-   * @param lockType
-   *        Identifies the lock type, e.g. "pin" for the PIN lock, "puk" for
-   *        the PUK lock.
-   *
-   * @return a DOM Request.
-   *         If the lock type is "pin", or "puk", the request's result will be
-   *         an object containing the number of retries for the specified
-   *         lock. For any other lock type, the result is undefined.
-   */
-  nsIDOMDOMRequest getCardLockRetryCount(in DOMString lockType);
-
-  // UICC Phonebook Interfaces.
+  [implicit_jscontext] attribute jsval oniccdetected;
 
   /**
-   * Read ICC contacts.
-   *
-   * @param contactType
-   *        One of type as below,
-   *        - 'adn': Abbreviated Dialling Number
-   *        - 'fdn': Fixed Dialling Number
-   */
-  nsIDOMDOMRequest readContacts(in DOMString contactType);
-
-  /**
-   * Update ICC Phonebook contact.
-   *
-   * @param contactType
-   *        One of type as below,
-   *        - 'adn': Abbreviated Dialling Number
-   *        - 'fdn': Fixed Dialling Number
-   * @param contact
-   *        The contact will be updated in ICC
-   * @param [optional] pin2
-   *        PIN2 is only required for 'fdn'.
-   */
-  nsIDOMDOMRequest updateContact(in DOMString contactType,
-                                 in jsval contact,
-                                 [optional] in DOMString pin2);
-
-  // End of UICC Phonebook Interfaces.
-
-  // UICC Secure Element Interfaces
-
-  /**
-   * A secure element is a smart card chip that can hold
-   * several different applications with the necessary security.
-   * The most known secure element is the Universal Integrated Circuit Card (UICC)
+   * 'oniccundetected' event is notified whenever an ICC becomes undetected.
    */
-
-  /**
-   * Send request to open a logical channel defined by its
-   * application identifier (AID)
-   *
-   * @param aid
-   *        The Application Identifier of the Applet to be selected on this channel
-   * return value : An instance of Channel (channelID) if available or null.
-   */
-  nsIDOMDOMRequest iccOpenChannel(in DOMString aid);
-
-  /**
-   * Interface, used to communicate with an applet through the
-   * Application Data Protocol Units (APDUs) and is
-   * used for all data that is exchanged between the UICC card and the terminal (ME).
-   *
-   * @param channel
-   *        The Application Identifier of the Applet to which APDU is directed
-   * @param apdu
-   *        Application Protocol Data Unit
-   * return value : Response APDU
-   */
-  nsIDOMDOMRequest iccExchangeAPDU(in long channel, in jsval apdu);
-
-  /**
-   * Send request to close the selected logical channel identified by its
-   * application identifier (AID)
-   *
-   * @param aid
-   *        The Application Identifier of the Applet , to be closed
-   */
-  nsIDOMDOMRequest iccCloseChannel(in long channel);
-
-  // End of UICC Secure Element Interfaces
+  [implicit_jscontext] attribute jsval oniccundetected;
 };
new file mode 100644
--- /dev/null
+++ b/dom/icc/src/Icc.cpp
@@ -0,0 +1,358 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "Icc.h"
+
+#include "mozilla/dom/MozIccBinding.h"
+#include "mozilla/dom/MozStkCommandEvent.h"
+#include "nsIDOMDOMRequest.h"
+#include "nsIDOMIccInfo.h"
+#include "nsJSON.h"
+#include "nsRadioInterfaceLayer.h"
+#include "nsServiceManagerUtils.h"
+
+using namespace mozilla::dom;
+
+Icc::Icc(nsPIDOMWindow* aWindow, long aClientId, const nsAString& aIccId)
+  : mLive(true)
+  , mClientId(aClientId)
+  , mIccId(aIccId)
+{
+  SetIsDOMBinding();
+  BindToOwner(aWindow);
+
+  mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
+
+  // Not being able to acquire the provider isn't fatal since we check
+  // for it explicitly below.
+  if (!mProvider) {
+    NS_WARNING("Could not acquire nsIIccProvider!");
+  }
+}
+
+void
+Icc::Shutdown()
+{
+  mProvider = nullptr;
+  mLive = false;
+}
+
+nsresult
+Icc::NotifyEvent(const nsAString& aName)
+{
+  return DispatchTrustedEvent(aName);
+}
+
+nsresult
+Icc::NotifyStkEvent(const nsAString& aName, const nsAString& aMessage)
+{
+  nsresult rv;
+  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  AutoPushJSContext cx(sc->GetNativeContext());
+  JS::Rooted<JS::Value> value(cx);
+
+  if (!aMessage.IsEmpty()) {
+    nsCOMPtr<nsIJSON> json(new nsJSON());
+    nsresult rv = json->DecodeToJSVal(aMessage, cx, value.address());
+    NS_ENSURE_SUCCESS(rv, rv);
+  } else {
+    value = JS::NullValue();
+  }
+
+  MozStkCommandEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mCommand = value;
+
+  nsRefPtr<MozStkCommandEvent> event =
+    MozStkCommandEvent::Constructor(this, aName, init);
+
+  return DispatchTrustedEvent(event);
+}
+
+// WrapperCache
+
+JSObject*
+Icc::WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope)
+{
+  return MozIccBinding::Wrap(aCx, aScope, this);
+}
+
+// MozIcc WebIDL
+
+already_AddRefed<nsIDOMMozIccInfo>
+Icc::GetIccInfo() const
+{
+  if (!mProvider) {
+    return nullptr;
+  }
+
+  nsCOMPtr<nsIDOMMozIccInfo> iccInfo;
+  nsresult rv = mProvider->GetIccInfo(mClientId, getter_AddRefs(iccInfo));
+  if (NS_FAILED(rv)) {
+    return nullptr;
+  }
+
+  return iccInfo.forget();
+}
+
+void
+Icc::GetCardState(nsString& aCardState) const
+{
+  aCardState.SetIsVoid(true);
+
+  if (!mProvider) {
+    return;
+  }
+
+  nsresult rv = mProvider->GetCardState(mClientId, aCardState);
+  if (NS_FAILED(rv)) {
+    aCardState.SetIsVoid(true);
+  }
+}
+
+void
+Icc::SendStkResponse(const JSContext* aCx, const JS::Value& aCommand,
+                     const JS::Value& aResponse, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsresult rv = mProvider->SendStkResponse(mClientId, GetOwner(), aCommand,
+                                           aResponse);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+void
+Icc::SendStkMenuSelection(uint16_t aItemIdentifier, bool aHelpRequested,
+                          ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsresult rv = mProvider->SendStkMenuSelection(mClientId,
+                                                GetOwner(),
+                                                aItemIdentifier,
+                                                aHelpRequested);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+void
+Icc::SendStkTimerExpiration(const JSContext* aCx, const JS::Value& aTimer,
+                            ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsresult rv = mProvider->SendStkTimerExpiration(mClientId, GetOwner(),
+                                                  aTimer);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+void
+Icc::SendStkEventDownload(const JSContext* aCx, const JS::Value& aEvent,
+                          ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return;
+  }
+
+  nsresult rv = mProvider->SendStkEventDownload(mClientId, GetOwner(), aEvent);
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+  }
+}
+
+already_AddRefed<nsISupports>
+Icc::GetCardLock(const nsAString& aLockType, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->GetCardLockState(mClientId, GetOwner(), aLockType,
+                                            getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::UnlockCardLock(const JSContext* aCx, const JS::Value& aInfo,
+                    ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->UnlockCardLock(mClientId, GetOwner(), aInfo,
+                                          getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::SetCardLock(const JSContext* aCx, const JS::Value& aInfo, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->SetCardLock(mClientId, GetOwner(), aInfo,
+                                       getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::GetCardLockRetryCount(const nsAString& aLockType, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->GetCardLockRetryCount(mClientId,
+                                                 GetOwner(),
+                                                 aLockType,
+                                                 getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::ReadContacts(const nsAString& aContactType, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->ReadContacts(mClientId, GetOwner(), aContactType,
+                                        getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::UpdateContact(const JSContext* aCx, const nsAString& aContactType,
+                   const JS::Value& aContact, const nsAString& aPin2,
+                   ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->UpdateContact(mClientId, GetOwner(), aContactType,
+                                         aContact, aPin2,
+                                         getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::IccOpenChannel(const nsAString& aAid, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->IccOpenChannel(mClientId, GetOwner(), aAid,
+                                          getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::IccExchangeAPDU(const JSContext* aCx, int32_t aChannel, const jsval& aApdu,
+                     ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->IccExchangeAPDU(mClientId, GetOwner(), aChannel,
+                                           aApdu, getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
+
+already_AddRefed<nsISupports>
+Icc::IccCloseChannel(int32_t aChannel, ErrorResult& aRv)
+{
+  if (!mProvider) {
+    aRv.Throw(NS_ERROR_FAILURE);
+    return nullptr;
+  }
+
+  nsRefPtr<nsIDOMDOMRequest> request;
+  nsresult rv = mProvider->IccCloseChannel(mClientId, GetOwner(), aChannel,
+                                           getter_AddRefs(request));
+  if (NS_FAILED(rv)) {
+    aRv.Throw(rv);
+    return nullptr;
+  }
+
+  return request.forget();
+}
new file mode 100644
--- /dev/null
+++ b/dom/icc/src/Icc.h
@@ -0,0 +1,117 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_Icc_h
+#define mozilla_dom_Icc_h
+
+#include "nsDOMEventTargetHelper.h"
+#include "nsIIccProvider.h"
+
+namespace mozilla {
+namespace dom {
+
+class Icc MOZ_FINAL : public nsDOMEventTargetHelper
+{
+public:
+  NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
+
+  Icc(nsPIDOMWindow* aWindow, long aClientId, const nsAString& aIccId);
+
+  void
+  Shutdown();
+
+  nsresult
+  NotifyEvent(const nsAString& aName);
+
+  nsresult
+  NotifyStkEvent(const nsAString& aName, const nsAString& aMessage);
+
+  nsString
+  GetIccId()
+  {
+    return mIccId;
+  }
+
+  nsPIDOMWindow*
+  GetParentObject() const
+  {
+    return GetOwner();
+  }
+
+  // WrapperCache
+  virtual JSObject*
+  WrapObject(JSContext* aCx, JS::Handle<JSObject*> aScope) MOZ_OVERRIDE;
+
+  // MozIcc WebIDL
+  already_AddRefed<nsIDOMMozIccInfo>
+  GetIccInfo() const;
+
+  void
+  GetCardState(nsString& aCardState) const;
+
+  void
+  SendStkResponse(const JSContext* aCx, const JS::Value& aCommand,
+                  const JS::Value& aResponse, ErrorResult& aRv);
+
+  void
+  SendStkMenuSelection(uint16_t aItemIdentifier, bool aHelpRequested,
+                       ErrorResult& aRv);
+
+  void
+  SendStkTimerExpiration(const JSContext* aCx, const JS::Value& aTimer,
+                         ErrorResult& aRv);
+
+  void
+  SendStkEventDownload(const JSContext* aCx, const JS::Value& aEvent,
+                       ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  GetCardLock(const nsAString& aLockType, ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  UnlockCardLock(const JSContext* aCx, const JS::Value& aInfo,
+                 ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  SetCardLock(const JSContext* aCx, const JS::Value& aInfo, ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  GetCardLockRetryCount(const nsAString& aLockType, ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  ReadContacts(const nsAString& aContactType, ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  UpdateContact(const JSContext* aCx, const nsAString& aContactType,
+                const JS::Value& aContact, const nsAString& aPin2,
+                ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  IccOpenChannel(const nsAString& aAid, ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  IccExchangeAPDU(const JSContext* aCx, int32_t aChannel, const jsval& aApdu,
+                  ErrorResult& aRv);
+
+  already_AddRefed<nsISupports>
+  IccCloseChannel(int32_t aChannel, ErrorResult& aRv);
+
+  IMPL_EVENT_HANDLER(iccinfochange)
+  IMPL_EVENT_HANDLER(cardstatechange)
+  IMPL_EVENT_HANDLER(stkcommand)
+  IMPL_EVENT_HANDLER(stksessionend)
+
+private:
+  bool mLive;
+  uint32_t mClientId;
+  nsString mIccId;
+  // mProvider is a xpcom service and will be released at shutdown, so it
+  // doesn't need to be cycle collected.
+  nsCOMPtr<nsIIccProvider> mProvider;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_icc_Icc_h
new file mode 100644
--- /dev/null
+++ b/dom/icc/src/IccListener.cpp
@@ -0,0 +1,130 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "IccListener.h"
+
+#include "Icc.h"
+#include "IccManager.h"
+#include "nsIDOMClassInfo.h"
+#include "nsIDOMIccInfo.h"
+#include "nsRadioInterfaceLayer.h"
+
+using namespace mozilla::dom;
+
+NS_IMPL_ISUPPORTS1(IccListener, nsIIccListener)
+
+IccListener::IccListener(IccManager* aIccManager, uint32_t aClientId)
+  : mClientId(aClientId)
+  , mIccManager(aIccManager)
+{
+  MOZ_ASSERT(mIccManager);
+
+  mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
+
+  if (!mProvider) {
+    NS_WARNING("Could not acquire nsIIccProvider!");
+    return;
+  }
+
+  nsCOMPtr<nsIDOMMozIccInfo> iccInfo;
+  mProvider->GetIccInfo(mClientId, getter_AddRefs(iccInfo));
+  if (iccInfo) {
+    nsString iccId;
+    iccInfo->GetIccid(iccId);
+    if (!iccId.IsEmpty()) {
+      mIcc = new Icc(mIccManager->GetOwner(), mClientId, iccId);
+    }
+  }
+
+  DebugOnly<nsresult> rv = mProvider->RegisterIccMsg(mClientId, this);
+  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
+                   "Failed registering icc messages with provider");
+}
+
+IccListener::~IccListener()
+{
+  Shutdown();
+}
+
+void
+IccListener::Shutdown()
+{
+  if (mProvider) {
+    mProvider->UnregisterIccMsg(mClientId, this);
+    mProvider = nullptr;
+  }
+
+  if (mIcc) {
+    mIcc->Shutdown();
+    mIcc = nullptr;
+  }
+
+  mIccManager = nullptr;
+}
+
+// nsIIccListener
+
+NS_IMETHODIMP
+IccListener::NotifyStkCommand(const nsAString& aMessage)
+{
+  if (!mIcc) {
+    return NS_OK;
+  }
+
+  return mIcc->NotifyStkEvent(NS_LITERAL_STRING("stkcommand"), aMessage);
+}
+
+NS_IMETHODIMP
+IccListener::NotifyStkSessionEnd()
+{
+  if (!mIcc) {
+    return NS_OK;
+  }
+
+  return mIcc->NotifyEvent(NS_LITERAL_STRING("stksessionend"));
+}
+
+NS_IMETHODIMP
+IccListener::NotifyCardStateChanged()
+{
+  if (!mIcc) {
+    return NS_OK;
+  }
+
+  return mIcc->NotifyEvent(NS_LITERAL_STRING("cardstatechange"));
+}
+
+NS_IMETHODIMP
+IccListener::NotifyIccInfoChanged()
+{
+  nsCOMPtr<nsIDOMMozIccInfo> iccInfo;
+  mProvider->GetIccInfo(mClientId, getter_AddRefs(iccInfo));
+
+  // Create/delete icc object based on current iccInfo.
+  // 1. If the mIcc is nullptr and iccInfo has valid data, create icc object and
+  //    notify mIccManager a new icc is added.
+  // 2. If the mIcc is not nullptr and iccInfo becomes to null, delete existed
+  //    icc object and notify mIccManager the icc is removed.
+  if (!mIcc) {
+    if (iccInfo) {
+      nsString iccId;
+      iccInfo->GetIccid(iccId);
+      if (!iccId.IsEmpty()) {
+        mIcc = new Icc(mIccManager->GetOwner(), mClientId, iccId);
+        mIccManager->NotifyIccAdd(iccId);
+        mIcc->NotifyEvent(NS_LITERAL_STRING("iccinfochange"));
+      }
+    }
+  } else {
+    mIcc->NotifyEvent(NS_LITERAL_STRING("iccinfochange"));
+    if (!iccInfo) {
+      nsString iccId = mIcc->GetIccId();
+      mIcc->Shutdown();
+      mIcc = nullptr;
+      mIccManager->NotifyIccRemove(iccId);
+    }
+  }
+
+  return NS_OK;
+}
new file mode 100644
--- /dev/null
+++ b/dom/icc/src/IccListener.h
@@ -0,0 +1,52 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_dom_IccListener_h
+#define mozilla_dom_IccListener_h
+
+#include "nsAutoPtr.h"
+#include "nsIIccProvider.h"
+
+namespace mozilla {
+namespace dom {
+
+class IccManager;
+class Icc;
+
+class IccListener : public nsIIccListener
+{
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIICCLISTENER
+
+  IccListener(IccManager* aIccManager, uint32_t aClientId);
+  ~IccListener();
+
+  void
+  Shutdown();
+
+  already_AddRefed<Icc>
+  GetIcc()
+  {
+    nsRefPtr<Icc> icc = mIcc;
+    return icc.forget();
+  }
+
+private:
+  uint32_t mClientId;
+  // We did not setup 'mIcc' and 'mIccManager' being a participant of cycle
+  // collection is because in Navigator->Invalidate() it will call
+  // mIccManager->Shutdown(), then IccManager will call Shutdown() of each
+  // IccListener, this will release the reference and break the cycle.
+  nsRefPtr<Icc> mIcc;
+  nsRefPtr<IccManager> mIccManager;
+  // mProvider is a xpcom service and will be released at shutdown, so it
+  // doesn't need to be cycle collected.
+  nsCOMPtr<nsIIccProvider> mProvider;
+};
+
+} // namespace dom
+} // namespace mozilla
+
+#endif // mozilla_dom_IccListener_h
\ No newline at end of file
--- a/dom/icc/src/IccManager.cpp
+++ b/dom/icc/src/IccManager.cpp
@@ -1,310 +1,178 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
-#include "mozilla/dom/IccManager.h"
+#include "IccManager.h"
 
 #include "GeneratedEvents.h"
-#include "mozilla/dom/MozStkCommandEvent.h"
+#include "Icc.h"
+#include "IccListener.h"
+#include "mozilla/dom/IccChangeEvent.h"
+#include "mozilla/Preferences.h"
 #include "mozilla/Services.h"
 #include "nsIDOMClassInfo.h"
 #include "nsIDOMIccInfo.h"
-#include "nsJSON.h"
-#include "SimToolKit.h"
-
-#define NS_RILCONTENTHELPER_CONTRACTID "@mozilla.org/ril/content-helper;1"
 
 using namespace mozilla::dom;
 
-class IccManager::Listener : public nsIIccListener
-{
-  IccManager* mIccManager;
-
-public:
-  NS_DECL_ISUPPORTS
-  NS_FORWARD_SAFE_NSIICCLISTENER(mIccManager)
-
-  Listener(IccManager* aIccManager)
-    : mIccManager(aIccManager)
-  {
-    MOZ_ASSERT(mIccManager);
-  }
-
-  void
-  Disconnect()
-  {
-    MOZ_ASSERT(mIccManager);
-    mIccManager = nullptr;
-  }
-};
-
-NS_IMPL_ISUPPORTS1(IccManager::Listener, nsIIccListener)
-
 DOMCI_DATA(MozIccManager, IccManager)
 
-NS_INTERFACE_MAP_BEGIN(IccManager)
+NS_IMPL_CYCLE_COLLECTION_CLASS(IccManager)
+
+NS_IMPL_CYCLE_COLLECTION_TRACE_BEGIN_INHERITED(IccManager,
+                                               nsDOMEventTargetHelper)
+  NS_IMPL_CYCLE_COLLECTION_TRACE_JS_MEMBER_CALLBACK(mJsIccIds)
+  // We did not setup 'mIccListeners' being a participant of cycle collection is
+  // because in Navigator->Invalidate() it will call mIccManager->Shutdown(),
+  // then IccManager will call Shutdown() of each IccListener, this will release
+  // the reference that held by each mIccListener and break the cycle.
+NS_IMPL_CYCLE_COLLECTION_TRACE_END
+
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN_INHERITED(IccManager,
+                                                  nsDOMEventTargetHelper)
+NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
+
+NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN_INHERITED(IccManager,
+                                                nsDOMEventTargetHelper)
+  tmp->Unroot();
+NS_IMPL_CYCLE_COLLECTION_UNLINK_END
+
+NS_INTERFACE_MAP_BEGIN_CYCLE_COLLECTION_INHERITED(IccManager)
   NS_INTERFACE_MAP_ENTRY(nsIDOMMozIccManager)
   NS_DOM_INTERFACE_MAP_ENTRY_CLASSINFO(MozIccManager)
 NS_INTERFACE_MAP_END_INHERITING(nsDOMEventTargetHelper)
 
 NS_IMPL_ADDREF_INHERITED(IccManager, nsDOMEventTargetHelper)
 NS_IMPL_RELEASE_INHERITED(IccManager, nsDOMEventTargetHelper)
 
-IccManager::IccManager()
+IccManager::IccManager(nsPIDOMWindow* aWindow)
+  : mJsIccIds(nullptr)
+  , mRooted(false)
 {
-  mProvider = do_GetService(NS_RILCONTENTHELPER_CONTRACTID);
-
-  // Not being able to acquire the provider isn't fatal since we check
-  // for it explicitly below.
-  if (!mProvider) {
-    NS_WARNING("Could not acquire nsIIccProvider!");
-    return;
-  }
+  BindToOwner(aWindow);
 
-  // TODO: Bug 814637 - WebIccManager API: support multiple sim cards
-  // In Multi-sim, there is more than one client in iccProvider. Each client
-  // represents a icc service. To maintain the backward compatibility with
-  // single sim, we always use client 0 for now. Adding support for multiple sim
-  // will be addressed in bug 814637.
-  mClientId = 0;
+  uint32_t numberOfServices =
+    mozilla::Preferences::GetUint("ril.numRadioInterfaces", 1);
 
-  mListener = new Listener(this);
-  DebugOnly<nsresult> rv = mProvider->RegisterIccMsg(mClientId, mListener);
-  NS_WARN_IF_FALSE(NS_SUCCEEDED(rv),
-                   "Failed registering icc messages with provider");
+  for (uint32_t i = 0; i < numberOfServices; i++) {
+    nsRefPtr<IccListener> iccListener = new IccListener(this, i);
+    mIccListeners.AppendElement(iccListener);
+  }
 }
 
-void
-IccManager::Init(nsPIDOMWindow* aWindow)
+IccManager::~IccManager()
 {
-  BindToOwner(aWindow);
+  Shutdown();
+  Unroot();
 }
 
 void
 IccManager::Shutdown()
 {
-  if (mProvider && mListener) {
-    mListener->Disconnect();
-    mProvider->UnregisterIccMsg(mClientId, mListener);
-    mProvider = nullptr;
-    mListener = nullptr;
+  for (uint32_t i = 0; i < mIccListeners.Length(); i++) {
+    mIccListeners[i]->Shutdown();
+    mIccListeners[i] = nullptr;
+  }
+  mIccListeners.Clear();
+}
+
+nsresult
+IccManager::NotifyIccAdd(const nsAString& aIccId)
+{
+  mJsIccIds = nullptr;
+
+  IccChangeEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mIccId = aIccId;
+
+  nsRefPtr<IccChangeEvent> event =
+    IccChangeEvent::Constructor(this, NS_LITERAL_STRING("iccdetected"), init);
+
+  return DispatchTrustedEvent(event);
+}
+
+nsresult
+IccManager::NotifyIccRemove(const nsAString& aIccId)
+{
+  mJsIccIds = nullptr;
+
+  IccChangeEventInit init;
+  init.mBubbles = false;
+  init.mCancelable = false;
+  init.mIccId = aIccId;
+
+  nsRefPtr<IccChangeEvent> event =
+    IccChangeEvent::Constructor(this, NS_LITERAL_STRING("iccundetected"), init);
+
+  return DispatchTrustedEvent(event);
+}
+
+void
+IccManager::Root()
+{
+  if (!mRooted) {
+    mozilla::HoldJSObjects(this);
+    mRooted = true;
+  }
+}
+
+void
+IccManager::Unroot()
+{
+  if (mRooted) {
+    mJsIccIds = nullptr;
+    mozilla::DropJSObjects(this);
+    mRooted = false;
   }
 }
 
 // nsIDOMMozIccManager
 
 NS_IMETHODIMP
-IccManager::SendStkResponse(const JS::Value& aCommand,
-                            const JS::Value& aResponse)
+IccManager::GetIccIds(JS::Value* aIccIds)
 {
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
+  if (!mJsIccIds) {
+    nsTArray<nsString> iccIds;
+    for (uint32_t i = 0; i < mIccListeners.Length(); i++) {
+      nsRefPtr<Icc> icc = mIccListeners[i]->GetIcc();
+      if (icc) {
+        iccIds.AppendElement(icc->GetIccId());
+      }
+    }
 
-  mProvider->SendStkResponse(mClientId, GetOwner(), aCommand, aResponse);
-  return NS_OK;
-}
+    nsresult rv;
+    nsIScriptContext* sc = GetContextForEventHandlers(&rv);
+    NS_ENSURE_SUCCESS(rv, rv);
 
-NS_IMETHODIMP
-IccManager::SendStkMenuSelection(uint16_t aItemIdentifier, bool aHelpRequested)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
+    AutoPushJSContext cx(sc->GetNativeContext());
+    JS::Rooted<JSObject*> jsIccIds(cx);
+    rv = nsTArrayToJSArray(cx, iccIds, jsIccIds.address());
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    mJsIccIds = jsIccIds;
+    Root();
   }
 
-  mProvider->SendStkMenuSelection(mClientId, GetOwner(), aItemIdentifier, aHelpRequested);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-IccManager::SendStkTimerExpiration(const JS::Value& aTimer)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  mProvider->SendStkTimerExpiration(mClientId, GetOwner(), aTimer);
-  return NS_OK;
-}
-
-NS_IMETHODIMP
-IccManager::SendStkEventDownload(const JS::Value& aEvent)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  mProvider->SendStkEventDownload(mClientId, GetOwner(), aEvent);
+  aIccIds->setObject(*mJsIccIds);
   return NS_OK;
 }
 
 NS_IMETHODIMP
-IccManager::GetIccInfo(nsIDOMMozIccInfo** aIccInfo)
+IccManager::GetIccById(const nsAString& aIccId, nsISupports** aIcc)
 {
-  *aIccInfo = nullptr;
-
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetIccInfo(mClientId, aIccInfo);
-}
-
-NS_IMETHODIMP
-IccManager::GetCardState(nsAString& cardState)
-{
-  cardState.SetIsVoid(true);
+  *aIcc = nullptr;
 
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCardState(mClientId, cardState);
-}
-
-NS_IMETHODIMP
-IccManager::GetCardLock(const nsAString& aLockType, nsIDOMDOMRequest** aDomRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCardLockState(mClientId, GetOwner(), aLockType, aDomRequest);
-}
-
-NS_IMETHODIMP
-IccManager::SetCardLock(const JS::Value& aInfo, nsIDOMDOMRequest** aDomRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
+  for (uint32_t i = 0; i < mIccListeners.Length(); i++) {
+    nsRefPtr<Icc> icc = mIccListeners[i]->GetIcc();
+    if (icc && aIccId == icc->GetIccId()) {
+      icc.forget(aIcc);
+      return NS_OK;
+    }
   }
 
-  return mProvider->SetCardLock(mClientId, GetOwner(), aInfo, aDomRequest);
-}
-
-NS_IMETHODIMP
-IccManager::UnlockCardLock(const JS::Value& aInfo, nsIDOMDOMRequest** aDomRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->UnlockCardLock(mClientId, GetOwner(), aInfo, aDomRequest);
-}
-
-NS_IMETHODIMP
-IccManager::GetCardLockRetryCount(const nsAString& aLockType, nsIDOMDOMRequest** aDomRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->GetCardLockRetryCount(mClientId, GetOwner(), aLockType, aDomRequest);
-}
-
-NS_IMETHODIMP
-IccManager::IccOpenChannel(const nsAString& aAid, nsIDOMDOMRequest** aRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->IccOpenChannel(mClientId, GetOwner(), aAid, aRequest);
-}
-
-NS_IMETHODIMP
-IccManager::IccExchangeAPDU(int32_t aChannel, const jsval& aApdu, nsIDOMDOMRequest** aRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->IccExchangeAPDU(mClientId, GetOwner(), aChannel, aApdu, aRequest);
+  return NS_OK;
 }
 
-NS_IMETHODIMP
-IccManager::IccCloseChannel(int32_t aChannel, nsIDOMDOMRequest** aRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->IccCloseChannel(mClientId, GetOwner(), aChannel, aRequest);
-}
-
-NS_IMETHODIMP
-IccManager::ReadContacts(const nsAString& aContactType, nsIDOMDOMRequest** aRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->ReadContacts(mClientId, GetOwner(), aContactType, aRequest);
-}
-
-NS_IMETHODIMP
-IccManager::UpdateContact(const nsAString& aContactType,
-                          const JS::Value& aContact,
-                          const nsAString& aPin2,
-                          nsIDOMDOMRequest** aRequest)
-{
-  if (!mProvider) {
-    return NS_ERROR_FAILURE;
-  }
-
-  return mProvider->UpdateContact(mClientId, GetOwner(), aContactType, aContact, aPin2, aRequest);
-}
-
-NS_IMPL_EVENT_HANDLER(IccManager, stkcommand)
-NS_IMPL_EVENT_HANDLER(IccManager, stksessionend)
-NS_IMPL_EVENT_HANDLER(IccManager, cardstatechange)
-NS_IMPL_EVENT_HANDLER(IccManager, iccinfochange)
-
-// nsIIccListener
-
-NS_IMETHODIMP
-IccManager::NotifyStkCommand(const nsAString& aMessage)
-{
-  nsresult rv;
-  nsIScriptContext* sc = GetContextForEventHandlers(&rv);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  AutoPushJSContext cx(sc->GetNativeContext());
-  JS::Rooted<JS::Value> value(cx);
-
-  if (!aMessage.IsEmpty()) {
-    nsCOMPtr<nsIJSON> json(new nsJSON());
-    nsresult rv = json->DecodeToJSVal(aMessage, cx, value.address());
-    NS_ENSURE_SUCCESS(rv, rv);
-  } else {
-    value = JSVAL_VOID;
-  }
-
-  MozStkCommandEventInit init;
-  init.mBubbles = false;
-  init.mCancelable = false;
-  init.mCommand = value;
-
-  nsRefPtr<MozStkCommandEvent> event =
-    MozStkCommandEvent::Constructor(this, NS_LITERAL_STRING("stkcommand"), init);
-
-  return DispatchTrustedEvent(event);
-}
-
-NS_IMETHODIMP
-IccManager::NotifyStkSessionEnd()
-{
-  return DispatchTrustedEvent(NS_LITERAL_STRING("stksessionend"));
-}
-
-NS_IMETHODIMP
-IccManager::NotifyCardStateChanged()
-{
-  return DispatchTrustedEvent(NS_LITERAL_STRING("cardstatechange"));
-}
-
-NS_IMETHODIMP
-IccManager::NotifyIccInfoChanged()
-{
-  return DispatchTrustedEvent(NS_LITERAL_STRING("iccinfochange"));
-}
+NS_IMPL_EVENT_HANDLER(IccManager, iccdetected)
+NS_IMPL_EVENT_HANDLER(IccManager, iccundetected)
--- a/dom/icc/src/IccManager.h
+++ b/dom/icc/src/IccManager.h
@@ -4,48 +4,56 @@
 
 #ifndef mozilla_dom_IccManager_h
 #define mozilla_dom_IccManager_h
 
 #include "nsCycleCollectionParticipant.h"
 #include "nsDOMEventTargetHelper.h"
 #include "nsIDOMIccManager.h"
 #include "nsIIccProvider.h"
+#include "nsTArrayHelpers.h"
 
 namespace mozilla {
 namespace dom {
 
-class IccManager : public nsDOMEventTargetHelper
-                 , public nsIDOMMozIccManager
+class IccListener;
+
+class IccManager MOZ_FINAL : public nsDOMEventTargetHelper
+                           , public nsIDOMMozIccManager
 {
-  /**
-   * Class IccManager doesn't actually inherit nsIIccListener. Instead, it owns
-   * an nsIIccListener derived instance mListener and passes it to
-   * nsIIccProvider. The onreceived events are first delivered to mListener and
-   * then forwarded to its owner, IccManager. See also bug 775997 comment #51.
-   */
-  class Listener;
-
 public:
   NS_DECL_ISUPPORTS_INHERITED
   NS_DECL_NSIDOMMOZICCMANAGER
-  NS_DECL_NSIICCLISTENER
 
   NS_REALLY_FORWARD_NSIDOMEVENTTARGET(nsDOMEventTargetHelper)
 
-  IccManager();
+  NS_DECL_CYCLE_COLLECTION_SCRIPT_HOLDER_CLASS_INHERITED(IccManager,
+                                                         nsDOMEventTargetHelper)
+
+  IccManager(nsPIDOMWindow* aWindow);
+  ~IccManager();
 
-  void Init(nsPIDOMWindow *aWindow);
-  void Shutdown();
+  void
+  Shutdown();
+
+  nsresult
+  NotifyIccAdd(const nsAString& aIccId);
+
+  nsresult
+  NotifyIccRemove(const nsAString& aIccId);
 
 private:
-  // TODO: Bug 814637 - WebIccManager API: support multiple sim cards
-  // The private member, mClient, will be moved to other proper place for
-  // supporting multiple sim cards.
-  uint32_t mClientId;
-  nsCOMPtr<nsIIccProvider> mProvider;
-  nsRefPtr<Listener> mListener;
+  nsTArray<nsRefPtr<IccListener>> mIccListeners;
+
+  // Cached iccIds js array object. Cleared whenever the NotifyIccAdd() or
+  // NotifyIccRemove() is called, and then rebuilt once a page looks for the
+  // iccIds attribute.
+  JS::Heap<JSObject*> mJsIccIds;
+  bool mRooted;
+
+  void Root();
+  void Unroot();
 };
 
 } // namespace dom
 } // namespace mozilla
 
 #endif // mozilla_dom_IccManager_h
--- a/dom/icc/src/moz.build
+++ b/dom/icc/src/moz.build
@@ -1,23 +1,28 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 EXPORTS.mozilla.dom += [
+    'Icc.h',
     'IccManager.h',
 ]
 
 SOURCES += [
+    'Icc.cpp',
+    'IccListener.cpp',
     'IccManager.cpp',
 ]
 
 FAIL_ON_WARNINGS = True
 
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'gklayout'
+
 LOCAL_INCLUDES += [
+    '../../system/gonk',
     '/content/events/src',
 ]
 
new file mode 100644
--- /dev/null
+++ b/dom/icc/tests/marionette/icc_header.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+SpecialPowers.addPermission("mobileconnection", true, document);
+
+let iccManager = navigator.mozIccManager;
+ok(iccManager instanceof MozIccManager,
+   "iccManager is instanceof " + iccManager.constructor);
+
+// TODO: Bug 932650 - B2G RIL: WebIccManager API - add marionette tests for
+//                    multi-sim
+// In single sim scenario, there is only one sim card, we can use below way to
+// check iccId and get icc object. But in multi-sim, the index of iccIds may
+// not map to sim slot directly, we should have a better way to handle this.
+let iccIds = iccManager.iccIds;
+ok(Array.isArray(iccIds), "iccIds is an array");
+is(iccIds.length, 1, "iccIds.length is " + iccIds.length);
+
+let iccId = iccIds[0];
+is(iccId, "89014103211118510720", "iccId is " + iccId);
+
+let icc = iccManager.getIccById(iccId);
+ok(icc instanceof MozIcc, "icc is instanceof " + icc.constructor);
+
+/* Remove permission and execute finish() */
+let cleanUp = function () {
+  SpecialPowers.removePermission("mobileconnection", document);
+  finish();
+};
+
+/* Helper for tasks */
+let taskHelper = {
+  tasks: [],
+
+  push: function(task) {
+    this.tasks.push(task);
+  },
+
+  runNext: function() {
+    let task = this.tasks.shift();
+    if (!task) {
+      cleanUp();
+      return;
+    }
+
+    if (typeof task === "function") {
+      task();
+    }
+  },
+};
+
+/* Helper for emulator console command */
+let emulatorHelper = {
+  pendingCommandCount: 0,
+
+  sendCommand: function(cmd, callback) {
+    this.pendingCommandCount++;
+    runEmulatorCmd(cmd, function(result) {
+      this.pendingCommandCount--;
+      is(result[result.length - 1], "OK");
+
+      if (callback && typeof callback === "function") {
+        callback(result);
+      }
+    });
+  },
+};
--- a/dom/icc/tests/marionette/manifest.ini
+++ b/dom/icc/tests/marionette/manifest.ini
@@ -17,8 +17,12 @@ qemu = true
 [test_stk_send_dtmf.js]
 [test_stk_launch_browser.js]
 [test_stk_display_text.js]
 [test_stk_get_inkey.js]
 [test_stk_get_input.js]
 [test_stk_select_item.js]
 [test_stk_setup_menu.js]
 [test_stk_setup_idle_mode_text.js]
+[test_icc_access_invalid_object.js]
+disabled = Bug 933654
+[test_icc_detected_undetected_event.js]
+disabled = Bug 933654
--- a/dom/icc/tests/marionette/stk_helper.js
+++ b/dom/icc/tests/marionette/stk_helper.js
@@ -1,17 +1,33 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
 
 SpecialPowers.addPermission("mobileconnection", true, document);
 
-let icc = navigator.mozIccManager;
-ok(icc instanceof MozIccManager, "icc is instanceof " + icc.constructor);
+let iccManager = navigator.mozIccManager;
+ok(iccManager instanceof MozIccManager,
+   "iccManager is instanceof " + iccManager.constructor);
+
+// TODO: Bug 932650 - B2G RIL: WebIccManager API - add marionette tests for
+//                    multi-sim
+// In single sim scenario, there is only one sim card, we can use below way to
+// check iccId and get icc object. But in multi-sim, the index of iccIds may
+// not map to sim slot directly, we should have a better way to handle this.
+let iccIds = iccManager.iccIds;
+ok(Array.isArray(iccIds), "iccIds is an array");
+is(iccIds.length, 1, "iccIds.length is " + iccIds.length);
+
+let iccId = iccIds[0];
+is(iccId, "89014103211118510720", "iccId is " + iccId);
+
+let icc = iccManager.getIccById(iccId);
+ok(icc instanceof MozIcc, "icc is instanceof " + icc.constructor);
 
 let pendingEmulatorCmdCount = 0;
 function sendStkPduToEmulator(command, func, expect) {
   ++pendingEmulatorCmdCount;
 
   runEmulatorCmd(command, function (result) {
     --pendingEmulatorCmdCount;
     is(result[0], "OK");
new file mode 100644
--- /dev/null
+++ b/dom/icc/tests/marionette/test_icc_access_invalid_object.js
@@ -0,0 +1,121 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 30000;
+MARIONETTE_HEAD_JS = "icc_header.js";
+
+function setRadioEnabled(enabled) {
+  SpecialPowers.addPermission("settings-write", true, document);
+
+  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
+  let settings = navigator.mozSettings;
+  let setLock = settings.createLock();
+  let obj = {
+    "ril.radio.disabled": !enabled
+  };
+  let setReq = setLock.set(obj);
+
+  setReq.addEventListener("success", function onSetSuccess() {
+    log("set 'ril.radio.disabled' to " + enabled);
+  });
+
+  setReq.addEventListener("error", function onSetError() {
+    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
+  });
+
+  SpecialPowers.removePermission("settings-write", document);
+}
+
+/* Test access invalid icc object */
+taskHelper.push(function testAccessRemovedIccObject() {
+  setRadioEnabled(false);
+  iccManager.addEventListener("iccundetected", function oniccundetected(evt) {
+    log("got icc undetected event");
+    iccManager.removeEventListener("iccundetected", oniccundetected);
+    is(evt.iccId, iccId, "icc " + evt.iccId + " becomes undetected");
+
+    // Test access iccInfo.
+    try {
+      is(icc.iccInfo, null, "iccInfo: expect to get null");
+    } catch(e) {
+      ok(false, "access iccInfo should not get exception");
+    }
+
+    // Test access cardState.
+    try {
+      is(icc.cardState, null, "cardState: expect to get null");
+    } catch(e) {
+      ok(false, "access cardState should not get exception");
+    }
+
+    // Test STK related function.
+    try {
+      icc.sendStkResponse({}, {});
+      ok(false, "sendStkResponse() should get exception");
+    } catch(e) {}
+    try {
+      icc.sendStkMenuSelection(0, false);
+      ok(false, "sendStkMenuSelection() should get exception");
+    } catch(e) {}
+    try {
+      icc.sendStkTimerExpiration({});
+      ok(false, "sendStkTimerExpiration() should get exception");
+    } catch(e) {}
+    try {
+      icc.sendStkEventDownload({});
+      ok(false, "sendStkEventDownload() should get exception");
+    } catch(e) {}
+
+    // Test card lock related function.
+    try {
+      icc.getCardLock("");
+      ok(false, "getCardLock() should get exception");
+    } catch(e) {}
+    try {
+      icc.unlockCardLock({});
+      ok(false, "unlockCardLock() should get exception");
+    } catch(e) {}
+    try {
+      icc.setCardLock({});
+      ok(false, "setCardLock() should get exception");
+    } catch(e) {}
+    try {
+      icc.getCardLockRetryCount("");
+      ok(false, "getCardLockRetryCount() should get exception");
+    } catch(e) {}
+
+    // Test contact related function.
+    try {
+      icc.readContacts("");
+      ok(false, "readContacts() should get exception");
+    } catch(e) {}
+    try {
+      icc.updateContact("", {});
+      ok(false, "updateContact() should get exception");
+    } catch(e) {}
+
+    // Test secure element related function.
+    try {
+      icc.iccOpenChannel("");
+      ok(false, "iccOpenChannel() should get exception");
+    } catch(e) {}
+    try {
+      icc.iccExchangeAPDU(0, {});
+      ok(false, "iccExchangeAPDU() should get exception");
+    } catch(e) {}
+    try {
+      icc.iccCloseChannel(0);
+      ok(false, "iccCloseChannel() should get exception");
+    } catch(e) {}
+
+    // We should restore the radio status.
+    setRadioEnabled(true);
+    iccManager.addEventListener("iccdetected", function oniccdetected(evt) {
+      iccManager.removeEventListener("iccdetected", oniccdetected);
+      taskHelper.runNext();
+    });
+  });
+});
+
+// Start test
+taskHelper.runNext();
--- a/dom/icc/tests/marionette/test_icc_card_lock.js
+++ b/dom/icc/tests/marionette/test_icc_card_lock.js
@@ -1,165 +1,134 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let icc = navigator.mozIccManager;
-ok(icc instanceof MozIccManager, "icc is instanceof " + icc.constructor);
-
-/* Reset pin retries by passing correct pin code. */
-function resetPinRetries(pin, callback) {
-  let request = icc.setCardLock(
-    {lockType: "pin",
-     pin: pin,
-     newPin: pin});
-
-  request.onsuccess = function onsuccess() {
-    callback();
-  };
-
-  request.onerror = function onerror() {
-    is(false, "Reset pin retries got error: " + request.error.name);
-    callback();
-  };
-}
+MARIONETTE_HEAD_JS = "icc_header.js";
 
 /* Test PIN code changes fail */
-function testPinChangeFailed() {
+taskHelper.push(function testPinChangeFailed() {
   // The default pin is '0000' in emulator
   let request = icc.setCardLock(
     {lockType: "pin",
      pin: "1111",
      newPin: "0000"});
 
   ok(request instanceof DOMRequest,
      "request instanceof " + request.constructor);
 
   request.onerror = function onerror() {
     is(request.error.name, "IncorrectPassword");
     is(request.error.lockType, "pin");
     // The default pin retries is 3, failed once becomes to 2
     is(request.error.retryCount, 2);
 
-    resetPinRetries("0000", runNextTest);
+    // Reset pin retries by passing correct pin code.
+    let resetRequest = icc.setCardLock(
+      {lockType: "pin",
+       pin: "0000",
+       newPin: "0000"});
+
+    resetRequest.onsuccess = function onsuccess() {
+      taskHelper.runNext();
+    };
+
+    resetRequest.onerror = function onerror() {
+      ok(false, "Reset pin retries got error: " + request.error.name);
+      taskHelper.runNext();
+    };
   };
-}
+});
 
 /* Test PIN code changes success */
-function testPinChangeSuccess() {
+taskHelper.push(function testPinChangeSuccess() {
   // The default pin is '0000' in emulator
   let request = icc.setCardLock(
     {lockType: "pin",
      pin: "0000",
      newPin: "0000"});
 
   ok(request instanceof DOMRequest,
      "request instanceof " + request.constructor);
 
   request.onerror = function onerror() {
     ok(false, "Should not fail, got error: " + request.error.name);
 
-    runNextTest();
+    taskHelper.runNext();
   };
 
   request.onsuccess = function onsuccess() {
     is(request.result.lockType, "pin");
     is(request.result.success, true);
 
-    runNextTest();
+    taskHelper.runNext();
   };
-}
+});
 
 /* Read PIN-lock retry count */
-function testPinCardLockRetryCount() {
+taskHelper.push(function testPinCardLockRetryCount() {
   let request = icc.getCardLockRetryCount('pin');
 
   ok(request instanceof DOMRequest,
      'request instanceof ' + request.constructor);
 
   request.onsuccess = function onsuccess() {
     is(request.result.lockType, 'pin',
         'lockType is ' + request.result.lockType);
     ok(request.result.retryCount >= 0,
         'retryCount is ' + request.result.retryCount);
-    runNextTest();
+    taskHelper.runNext();
   };
   request.onerror = function onerror() {
     // The operation is optional any might not be supported for all
     // all locks. In this case, we generate 'NotSupportedError' for
     // the valid lock types.
     is(request.error.name, 'RequestNotSupported',
         'error name is ' + request.error.name);
-    runNextTest();
+    taskHelper.runNext();
   };
-}
+});
 
 /* Read PUK-lock retry count */
-function testPukCardLockRetryCount() {
+taskHelper.push(function testPukCardLockRetryCount() {
   let request = icc.getCardLockRetryCount('puk');
 
   ok(request instanceof DOMRequest,
      'request instanceof ' + request.constructor);
 
   request.onsuccess = function onsuccess() {
     is(request.result.lockType, 'puk',
         'lockType is ' + request.result.lockType);
     ok(request.result.retryCount >= 0,
         'retryCount is ' + request.result.retryCount);
-    runNextTest();
+    taskHelper.runNext();
   };
   request.onerror = function onerror() {
     // The operation is optional any might not be supported for all
     // all locks. In this case, we generate 'NotSupportedError' for
     // the valid lock types.
     is(request.error.name, 'RequestNotSupported',
         'error name is ' + request.error.name);
-    runNextTest();
+    taskHelper.runNext();
   };
-}
+});
 
 /* Read lock retry count for an invalid entries  */
-function testInvalidCardLockRetryCount() {
+taskHelper.push(function testInvalidCardLockRetryCount() {
   let request = icc.getCardLockRetryCount('invalid-lock-type');
 
   ok(request instanceof DOMRequest,
      'request instanceof ' + request.constructor);
 
   request.onsuccess = function onsuccess() {
     ok(false,
         'request should never return success for an invalid lock type');
-    runNextTest();
+    taskHelper.runNext();
   };
   request.onerror = function onerror() {
     is(request.error.name, 'GenericFailure',
         'error name is ' + request.error.name);
-    runNextTest();
+    taskHelper.runNext();
   };
-}
-
-let tests = [
-  testPinChangeFailed,
-  testPinChangeSuccess,
-  testPinCardLockRetryCount,
-  testPukCardLockRetryCount,
-  testInvalidCardLockRetryCount
-];
+});
 
-function runNextTest() {
-  let test = tests.shift();
-  if (!test) {
-    cleanUp();
-    return;
-  }
-
-  test();
-}
-
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-
-  finish();
-}
-
-runNextTest();
+// Start test
+taskHelper.runNext();
--- a/dom/icc/tests/marionette/test_icc_card_state.js
+++ b/dom/icc/tests/marionette/test_icc_card_state.js
@@ -1,70 +1,56 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-SpecialPowers.addPermission("settings-write", true, document);
-
-// Permission changes can't change existing Navigator.prototype
-// objects, so grab our objects from a new Navigator
-let ifr = document.createElement("iframe");
-let icc;
-ifr.onload = function() {
-  icc = ifr.contentWindow.navigator.mozIccManager;
+MARIONETTE_HEAD_JS = "icc_header.js";
 
-  ok(icc instanceof ifr.contentWindow.MozIccManager,
-     "icc is instanceof " + icc.constructor);
-
-  is(icc.cardState, "ready");
+function setRadioEnabled(enabled) {
+  SpecialPowers.addPermission("settings-write", true, document);
 
-  // Enable Airplane mode, expect got cardstatechange to null
-  testCardStateChange(true, null,
-    // Disable Airplane mode, expect got cardstatechange to 'ready'
-    testCardStateChange.bind(window, false, "ready", cleanUp)
-  );
-};
-document.body.appendChild(ifr);
-
-function setAirplaneModeEnabled(enabled) {
-  let settings = ifr.contentWindow.navigator.mozSettings;
+  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
+  let settings = navigator.mozSettings;
   let setLock = settings.createLock();
   let obj = {
-    "ril.radio.disabled": enabled
+    "ril.radio.disabled": !enabled
   };
   let setReq = setLock.set(obj);
 
-  log("set airplane mode to " + enabled);
-
   setReq.addEventListener("success", function onSetSuccess() {
     log("set 'ril.radio.disabled' to " + enabled);
   });
 
   setReq.addEventListener("error", function onSetError() {
     ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
   });
-}
 
-function waitCardStateChangedEvent(expectedCardState, callback) {
-  icc.addEventListener("cardstatechange", function oncardstatechange() {
-    log("card state changes to " + icc.cardState);
-    if (icc.cardState === expectedCardState) {
-      log("got expected card state: " + icc.cardState);
-      icc.removeEventListener("cardstatechange", oncardstatechange);
-      callback();
-    }
-  });
+  SpecialPowers.removePermission("settings-write", document);
 }
 
-// Test cardstatechange event by switching airplane mode
-function testCardStateChange(airplaneMode, expectedCardState, callback) {
-  setAirplaneModeEnabled(airplaneMode);
-  waitCardStateChangedEvent(expectedCardState, callback);
-}
+/* Basic test */
+taskHelper.push(function basicTest() {
+  is(icc.cardState, "ready", "card state is " + icc.cardState);
+  taskHelper.runNext();
+});
 
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  SpecialPowers.removePermission("settings-write", document);
+/* Test cardstatechange event by switching radio off */
+taskHelper.push(function testCardStateChange() {
+  // Turn off radio.
+  setRadioEnabled(false);
+  icc.addEventListener("cardstatechange", function oncardstatechange() {
+    log("card state changes to " + icc.cardState);
+    // Expect to get card state changing to null.
+    if (icc.cardState === null) {
+      icc.removeEventListener("cardstatechange", oncardstatechange);
+      // We should restore radio status and expect to get iccdetected event.
+      setRadioEnabled(true);
+      iccManager.addEventListener("iccdetected", function oniccdetected(evt) {
+        log("icc iccdetected: " + evt.iccId);
+        iccManager.removeEventListener("iccdetected", oniccdetected);
+        taskHelper.runNext();
+      });
+    }
+  });
+});
 
-  finish();
-}
+// Start test
+taskHelper.runNext();
--- a/dom/icc/tests/marionette/test_icc_contact.js
+++ b/dom/icc/tests/marionette/test_icc_contact.js
@@ -1,17 +1,13 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 60000;
-
-SpecialPowers.addPermission("mobileconnection", true, document);
-
-let icc = navigator.mozIccManager;
-ok(icc instanceof MozIccManager, "icc is instanceof " + icc.constructor);
+MARIONETTE_HEAD_JS = "icc_header.js";
 
 function testReadContacts(type) {
   let request = icc.readContacts(type);
   request.onsuccess = function onsuccess() {
     let contacts = request.result;
 
     is(Array.isArray(contacts), true);
 
@@ -26,24 +22,24 @@ function testReadContacts(type) {
     is(contacts[2].name[0], "Fire 火");
     is(contacts[2].tel[0].value, "15555218203");
     is(contacts[2].id, "890141032111185107203");
 
     is(contacts[3].name[0], "Huang 黃");
     is(contacts[3].tel[0].value, "15555218204");
     is(contacts[3].id, "890141032111185107204");
 
-    runNextTest();
+    taskHelper.runNext();
   };
 
   request.onerror = function onerror() {
     ok(false, "Cannot get " + type + " contacts");
-    runNextTest();
+    taskHelper.runNext();
   };
-};
+}
 
 function testAddContact(type, pin2) {
   let contact = new mozContact({
     name: ["add"],
     tel: [{value: "0912345678"}],
     email:[]
   });
 
@@ -58,68 +54,55 @@ function testAddContact(type, pin2) {
       let contacts = getRequest.result;
 
       // There are 4 SIM contacts which are harded in emulator
       is(contacts.length, 5);
 
       is(contacts[4].name[0], "add");
       is(contacts[4].tel[0].value, "0912345678");
 
-      runNextTest();
+      taskHelper.runNext();
     };
 
     getRequest.onerror = function onerror() {
       ok(false, "Cannot get " + type + " contacts: " + getRequest.error.name);
-      runNextTest();
+      taskHelper.runNext();
     };
   };
 
   updateRequest.onerror = function onerror() {
     if (type === "fdn" && pin2 === undefined) {
       ok(updateRequest.error.name === "pin2 is empty",
          "expected error when pin2 is not provided");
     } else {
       ok(false, "Cannot add " + type + " contact: " + updateRequest.error.name);
     }
-    runNextTest();
+    taskHelper.runNext();
   };
-};
-
-function testReadAdnContacts() {
-  testReadContacts("adn");
-}
-
-function testAddAdnContact() {
-  testAddContact("adn");
-}
-
-function testReadFdnContacts() {
-  testReadContacts("fdn");
-}
-
-function testAddFdnContact() {
-  testAddContact("fdn", "0000");
-  testAddContact("fdn");
 }
 
-let tests = [
-  testReadAdnContacts,
-  testAddAdnContact,
-  testReadFdnContacts,
-  testAddFdnContact
-];
+/* Test read adn contacts */
+taskHelper.push(function testReadAdnContacts() {
+  testReadContacts("adn");
+});
+
+/* Test add adn contacts */
+taskHelper.push(function testAddAdnContact() {
+  testAddContact("adn");
+});
 
-function runNextTest() {
-  let test = tests.pop();
-  if (!test) {
-    cleanUp();
-    return;
-  }
+/* Test read fdn contacts */
+taskHelper.push(function testReadAdnContacts() {
+  testReadContacts("fdn");
+});
 
-  test();
-}
+/* Test add fdn contacts */
+taskHelper.push(function testReadAdnContacts() {
+  testAddContact("fdn", "0000");
+});
 
-function cleanUp() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  finish();
-}
+/* Test add fdn contacts without passing pin2 */
+taskHelper.push(function testReadAdnContacts() {
+  testAddContact("fdn");
+});
 
-runNextTest();
+// Start test
+taskHelper.runNext();
new file mode 100644
--- /dev/null
+++ b/dom/icc/tests/marionette/test_icc_detected_undetected_event.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+   http://creativecommons.org/publicdomain/zero/1.0/ */
+
+MARIONETTE_TIMEOUT = 30000;
+MARIONETTE_HEAD_JS = "icc_header.js";
+
+function setRadioEnabled(enabled) {
+  SpecialPowers.addPermission("settings-write", true, document);
+
+  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
+  let settings = navigator.mozSettings;
+  let setLock = settings.createLock();
+  let obj = {
+    "ril.radio.disabled": !enabled
+  };
+  let setReq = setLock.set(obj);
+
+  setReq.addEventListener("success", function onSetSuccess() {
+    log("set 'ril.radio.disabled' to " + enabled);
+  });
+
+  setReq.addEventListener("error", function onSetError() {
+    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
+  });
+
+  SpecialPowers.removePermission("settings-write", document);
+}
+
+/* Test iccundetected event */
+taskHelper.push(function testIccUndetectedEvent() {
+  setRadioEnabled(false);
+  iccManager.addEventListener("iccundetected", function oniccundetected(evt) {
+    log("got icc undetected event");
+    iccManager.removeEventListener("iccundetected", oniccundetected);
+
+    // TODO: Bug 932650 - B2G RIL: WebIccManager API - add marionette tests for
+    //                    multi-sim
+    // In single sim scenario, there is only one sim card, we can use below way
+    // to check iccIds.
+    is(evt.iccId, iccId, "icc " + evt.iccId + " becomes undetected");
+    is(iccManager.iccIds.length, 0,
+       "iccIds.length becomes to " + iccManager.iccIds.length);
+    is(iccManager.getIccById(evt.iccId), null,
+       "should not get a valid icc object here");
+
+    taskHelper.runNext();
+  });
+});
+
+/* Test iccdetected event */
+taskHelper.push(function testIccDetectedEvent() {
+  setRadioEnabled(true);
+  iccManager.addEventListener("iccdetected", function oniccdetected(evt) {
+    log("got icc detected event");
+    iccManager.removeEventListener("iccdetected", oniccdetected);
+
+    // TODO: Bug 932650 - B2G RIL: WebIccManager API - add marionette tests for
+    //                    multi-sim
+    // In single sim scenario, there is only one sim card, we can use below way
+    // to check iccIds.
+    is(evt.iccId, iccId, "icc " + evt.iccId + " is detected");
+    is(iccManager.iccIds.length, 1,
+       "iccIds.length becomes to " + iccManager.iccIds.length);
+    ok(iccManager.getIccById(evt.iccId) instanceof MozIcc,
+       "should get a valid icc object here");
+
+    taskHelper.runNext();
+  });
+});
+
+// Start test
+taskHelper.runNext();
--- a/dom/icc/tests/marionette/test_icc_info.js
+++ b/dom/icc/tests/marionette/test_icc_info.js
@@ -1,156 +1,111 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_TIMEOUT = 30000;
+MARIONETTE_HEAD_JS = "icc_header.js";
 
-SpecialPowers.addPermission("mobileconnection", true, document);
+function setRadioEnabled(enabled) {
+  SpecialPowers.addPermission("settings-write", true, document);
+
+  // TODO: Bug 856553 - [B2G] RIL: need an API to enable/disable radio
+  let settings = navigator.mozSettings;
+  let setLock = settings.createLock();
+  let obj = {
+    "ril.radio.disabled": !enabled
+  };
+  let setReq = setLock.set(obj);
+
+  setReq.addEventListener("success", function onSetSuccess() {
+    log("set 'ril.radio.disabled' to " + enabled);
+  });
 
-// Permission changes can't change existing Navigator.prototype
-// objects, so grab our objects from a new Navigator
-let ifr = document.createElement("iframe");
-let icc;
-let iccInfo;
-ifr.onload = function() {
-  icc = ifr.contentWindow.navigator.mozIccManager;
-  ok(icc instanceof ifr.contentWindow.MozIccManager,
-     "icc is instanceof " + icc.constructor);
+  setReq.addEventListener("error", function onSetError() {
+    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
+  });
+
+  SpecialPowers.removePermission("settings-write", document);
+}
 
-  iccInfo = icc.iccInfo;
+function setEmulatorMccMnc(mcc, mnc) {
+  let cmd = "operator set 0 Android,Android," + mcc + mnc;
+  emulatorHelper.sendCommand(cmd, function (result) {
+    let re = new RegExp("" + mcc + mnc + "$");
+    ok(result[0].match(re), "MCC/MNC should be changed.");
+  });
+}
+
+/* Basic test */
+taskHelper.push(function basicTest() {
+  let iccInfo = icc.iccInfo;
 
   is(iccInfo.iccType, "sim");
-
   // The emulator's hard coded iccid value.
   // See it here {B2G_HOME}/external/qemu/telephony/sim_card.c#L299.
   is(iccInfo.iccid, 89014103211118510720);
-
   // The emulator's hard coded mcc and mnc codes.
   // See it here {B2G_HOME}/external/qemu/telephony/android_modem.c#L2465.
   is(iccInfo.mcc, 310);
   is(iccInfo.mnc, 260);
   is(iccInfo.spn, "Android");
   // Phone number is hardcoded in MSISDN
   // See {B2G_HOME}/external/qemu/telephony/sim_card.c, in asimcard_io()
   is(iccInfo.msisdn, "15555215554");
 
-  runNextTest();
-};
-document.body.appendChild(ifr);
-
-let emulatorCmdPendingCount = 0;
-function sendEmulatorCommand(cmd, callback) {
-  emulatorCmdPendingCount++;
-  runEmulatorCmd(cmd, function (result) {
-    emulatorCmdPendingCount--;
-    is(result[result.length - 1], "OK");
-    callback(result);
-  });
-}
-
-function setEmulatorMccMnc(mcc, mnc) {
-  let cmd = "operator set 0 Android,Android," + mcc + mnc;
-  sendEmulatorCommand(cmd, function (result) {
-    let re = new RegExp("" + mcc + mnc + "$");
-    ok(result[0].match(re), "MCC/MNC should be changed.");
-  });
-}
-
-function setAirplaneModeEnabled(enabled) {
-  let settings = ifr.contentWindow.navigator.mozSettings;
-  let setLock = settings.createLock();
-  let obj = {
-    "ril.radio.disabled": enabled
-  };
-  let setReq = setLock.set(obj);
-
-  log("set airplane mode to " + enabled);
-
-  setReq.addEventListener("success", function onSetSuccess() {
-    log("set 'ril.radio.disabled' to " + enabled);
-  });
-
-  setReq.addEventListener("error", function onSetError() {
-    ok(false, "cannot set 'ril.radio.disabled' to " + enabled);
-  });
-}
-
-function waitForIccInfoChange(callback) {
-  icc.addEventListener("iccinfochange", function handler() {
-    icc.removeEventListener("iccinfochange", handler);
-    callback();
-  });
-}
+  taskHelper.runNext();
+});
 
-function waitForCardStateChange(expectedCardState, callback) {
-  icc.addEventListener("cardstatechange", function oncardstatechange() {
-    log("card state changes to " + icc.cardState);
-    if (icc.cardState === expectedCardState) {
-      log("got expected card state: " + icc.cardState);
-      icc.removeEventListener("cardstatechange", oncardstatechange);
-      callback();
-    }
-  });
-}
-
-// Test display condition change.
-function testDisplayConditionChange(func, caseArray, oncomplete) {
-  (function do_call(index) {
-    let next = index < (caseArray.length - 1) ? do_call.bind(null, index + 1) : oncomplete;
-    caseArray[index].push(next);
-    func.apply(null, caseArray[index]);
-  })(0);
-}
+/* Test display condition change */
+taskHelper.push(function testDisplayConditionChange() {
+  function testSPN(mcc, mnc, expectedIsDisplayNetworkNameRequired,
+                   expectedIsDisplaySpnRequired, callback) {
+    icc.addEventListener("iccinfochange", function handler() {
+      icc.removeEventListener("iccinfochange", handler);
+      is(icc.iccInfo.isDisplayNetworkNameRequired,
+         expectedIsDisplayNetworkNameRequired);
+      is(icc.iccInfo.isDisplaySpnRequired,
+         expectedIsDisplaySpnRequired);
+      // operatorchange will be ignored if we send commands too soon.
+      window.setTimeout(callback, 100);
+    });
+    // Send emulator command to change network mcc and mnc.
+    setEmulatorMccMnc(mcc, mnc);
+  }
 
-function testSPN(mcc, mnc, expectedIsDisplayNetworkNameRequired,
-                  expectedIsDisplaySpnRequired, callback) {
-  waitForIccInfoChange(function() {
-    is(iccInfo.isDisplayNetworkNameRequired,
-       expectedIsDisplayNetworkNameRequired);
-    is(iccInfo.isDisplaySpnRequired,
-       expectedIsDisplaySpnRequired);
-    // operatorchange will be ignored if we send commands too soon.
-    window.setTimeout(callback, 100);
-  });
-  setEmulatorMccMnc(mcc, mnc);
-}
-
-// Test iccInfo when card is not ready
-function testCardIsNotReady() {
-  // Enable airplane mode
-  setAirplaneModeEnabled(true);
-
-  waitForCardStateChange(null, function callback() {
-    is(icc.iccInfo, null);
-
-    // Disable airplane mode
-    setAirplaneModeEnabled(false);
-    waitForCardStateChange("ready", runNextTest);
-  });
-}
-
-let tests = [
-  testDisplayConditionChange.bind(this, testSPN, [
+  let testCases = [
     // [MCC, MNC, isDisplayNetworkNameRequired, isDisplaySpnRequired]
     [123, 456, false, true], // Not in HPLMN.
     [234, 136,  true, true], // Not in HPLMN, but in PLMN specified in SPDI.
     [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
     [466,  92,  true, true], // Not in HPLMN, but in another PLMN specified in SPDI.
     [123, 456, false, true], // Not in HPLMN. Triggering iccinfochange
     [310, 260,  true, true], // inside HPLMN.
-  ], runNextTest),
-  testCardIsNotReady
-];
+  ];
+
+  (function do_call(index) {
+    let next = index < (testCases.length - 1) ? do_call.bind(null, index + 1) : taskHelper.runNext.bind(taskHelper);
+    testCases[index].push(next);
+    testSPN.apply(null, testCases[index]);
+  })(0);
+});
 
-function runNextTest() {
-  let test = tests.shift();
-  if (!test) {
-    finalize();
-    return;
-  }
+/* Test iccInfo when card becomes undetected */
+taskHelper.push(function testCardIsNotReady() {
+  // Turn off radio.
+  setRadioEnabled(false);
+  icc.addEventListener("iccinfochange", function oniccinfochange() {
+    // Expect iccInfo changes to null
+    if (icc.iccInfo === null) {
+      icc.removeEventListener("iccinfochange", oniccinfochange);
+      // We should restore radio status and expect to get iccdetected event.
+      setRadioEnabled(true);
+      iccManager.addEventListener("iccdetected", function oniccdetected(evt) {
+        log("icc detected: " + evt.iccId);
+        iccManager.removeEventListener("iccdetected", oniccdetected);
+        taskHelper.runNext();
+      });
+    }
+  });
+});
 
-  test();
-}
-
-function finalize() {
-  SpecialPowers.removePermission("mobileconnection", document);
-  finish();
-}
+// Start test
+taskHelper.runNext();
--- a/dom/icc/tests/marionette/test_stk_display_text.js
+++ b/dom/icc/tests/marionette/test_stk_display_text.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testDisplayText(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_DISPLAY_TEXT, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_DISPLAY_TEXT, expect.name);
   is(command.options.text, expect.text, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.userClear, expect.userClear, expect.name);
   is(command.options.isHighPriority, expect.isHighPriority, expect.name);
 
   let duration = command.options.duration;
   if (duration) {
     is(duration.timeUnit, expect.duration.timeUnit, expect.name);
@@ -82,13 +82,13 @@ let tests = [
              text: "Saldo 2.04 E. Validez 20/05/13. ",
              userClear: true}},
   {command: "d0198103012180820281028D0A043130205365636F6E648402010A",
    func: testDisplayText,
    expect: {name: "display_text_cmd_11",
             commandQualifier: 0x80,
             text: "10 Second",
             userClear: true,
-            duration: {timeUnit: icc.STK_TIME_UNIT_SECOND,
+            duration: {timeUnit: iccManager.STK_TIME_UNIT_SECOND,
                        timeInterval: 0x0A}}},
 ];
 
 runNextTest();
--- a/dom/icc/tests/marionette/test_stk_get_inkey.js
+++ b/dom/icc/tests/marionette/test_stk_get_inkey.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testGetInKey(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_GET_INKEY, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_GET_INKEY, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.text, expect.text, expect.name);
   is(command.options.isAlphabet, expect.isAlphabet, expect.name);
   is(command.options.isUCS2, expect.isUCS2, expect.name);
   is(command.options.isYesNoRequested, expect.isYesNoRequested, expect.name);
 
   let duration = command.options.duration;
   if (duration) {
@@ -93,13 +93,13 @@ let tests = [
    expect: {name: "get_inkey_cmd_13",
             commandQualifier: 0x00,
             text: "<NO-ICON>"}},
   {command: "D0198103012200820281828D0A04456E74657220222B228402010A",
    func: testGetInKey,
    expect: {name: "get_inkey_cmd_14",
             commandQualifier: 0x00,
             text: "Enter \"+\"",
-            duration: {timeUnit: icc.STK_TIME_UNIT_SECOND,
+            duration: {timeUnit: iccManager.STK_TIME_UNIT_SECOND,
                        timeInterval: 0x0A}}},
 ];
 
 runNextTest();
--- a/dom/icc/tests/marionette/test_stk_get_input.js
+++ b/dom/icc/tests/marionette/test_stk_get_input.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testGetInput(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_GET_INPUT, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_GET_INPUT, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.text, expect.text, expect.name);
   is(command.options.minLength, expect.minLength, expect.name);
   is(command.options.maxLength, expect.maxLength, expect.name);
   if (command.options.defaultText) {
     is(command.options.defaultText, expect.defaultText, expect.name);
   }
   if (command.options.isAlphabet) {
--- a/dom/icc/tests/marionette/test_stk_launch_browser.js
+++ b/dom/icc/tests/marionette/test_stk_launch_browser.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testLaunchBrowser(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_LAUNCH_BROWSER, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_LAUNCH_BROWSER, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.url, expect.url, expect.name);
   if (command.options.confirmMessage) {
     is(command.options.confirmMessage, expect.text, expect.name);
   }
 
   runNextTest();
 }
--- a/dom/icc/tests/marionette/test_stk_poll_off.js
+++ b/dom/icc/tests/marionette/test_stk_poll_off.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testPollOff(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_POLL_OFF, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_POLL_OFF, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
 
   runNextTest();
 }
 
 let tests = [
   {command: "d009810301040082028182",
    func: testPollOff,
--- a/dom/icc/tests/marionette/test_stk_proactive_command.js
+++ b/dom/icc/tests/marionette/test_stk_proactive_command.js
@@ -1,92 +1,92 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testLocalInfoLocation(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_PROVIDE_LOCAL_INFO);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_PROVIDE_LOCAL_INFO);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_LOCAL_INFO_LOCATION_INFO);
-  is(cmd.options.localInfoType, icc.STK_LOCAL_INFO_LOCATION_INFO);
+  is(cmd.commandQualifier, iccManager.STK_LOCAL_INFO_LOCATION_INFO);
+  is(cmd.options.localInfoType, iccManager.STK_LOCAL_INFO_LOCATION_INFO);
 
   runNextTest();
 }
 
 function testLocalInfoImei(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_PROVIDE_LOCAL_INFO);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_PROVIDE_LOCAL_INFO);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_LOCAL_INFO_IMEI);
-  is(cmd.options.localInfoType, icc.STK_LOCAL_INFO_IMEI);
+  is(cmd.commandQualifier, iccManager.STK_LOCAL_INFO_IMEI);
+  is(cmd.options.localInfoType, iccManager.STK_LOCAL_INFO_IMEI);
 
   runNextTest();
 }
 
 function testLocalInfoDate(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_PROVIDE_LOCAL_INFO);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_PROVIDE_LOCAL_INFO);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_LOCAL_INFO_DATE_TIME_ZONE);
-  is(cmd.options.localInfoType, icc.STK_LOCAL_INFO_DATE_TIME_ZONE);
+  is(cmd.commandQualifier, iccManager.STK_LOCAL_INFO_DATE_TIME_ZONE);
+  is(cmd.options.localInfoType, iccManager.STK_LOCAL_INFO_DATE_TIME_ZONE);
 
   runNextTest();
 }
 
 function testLocalInfoLanguage(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_PROVIDE_LOCAL_INFO);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_PROVIDE_LOCAL_INFO);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_LOCAL_INFO_LANGUAGE);
-  is(cmd.options.localInfoType, icc.STK_LOCAL_INFO_LANGUAGE);
+  is(cmd.commandQualifier, iccManager.STK_LOCAL_INFO_LANGUAGE);
+  is(cmd.options.localInfoType, iccManager.STK_LOCAL_INFO_LANGUAGE);
 
   runNextTest();
 }
 
 function testRefresh(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_REFRESH);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_REFRESH);
   is(cmd.commandNumber, 0x01);
   is(cmd.commandQualifier, 0x01);
   is(cmd.options, null);
 
   runNextTest();
 }
 
 function testTimerManagementStart(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_TIMER_MANAGEMENT);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_TIMER_MANAGEMENT);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_TIMER_START);
-  is(cmd.options.timerAction, icc.STK_TIMER_START);
+  is(cmd.commandQualifier, iccManager.STK_TIMER_START);
+  is(cmd.options.timerAction, iccManager.STK_TIMER_START);
   is(cmd.options.timerId, 0x01);
   is(cmd.options.timerValue, (0x01 * 60 * 60) + (0x02 * 60) + 0x03);
 
   runNextTest();
 }
 
 function testTimerManagementDeactivate(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_TIMER_MANAGEMENT);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_TIMER_MANAGEMENT);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_TIMER_DEACTIVATE);
-  is(cmd.options.timerAction, icc.STK_TIMER_DEACTIVATE);
+  is(cmd.commandQualifier, iccManager.STK_TIMER_DEACTIVATE);
+  is(cmd.options.timerAction, iccManager.STK_TIMER_DEACTIVATE);
   is(cmd.options.timerId, 0x04);
 
   runNextTest();
 }
 
 function testTimerManagementGetCurrentValue(cmd) {
   log("STK CMD " + JSON.stringify(cmd));
-  is(cmd.typeOfCommand, icc.STK_CMD_TIMER_MANAGEMENT);
+  is(cmd.typeOfCommand, iccManager.STK_CMD_TIMER_MANAGEMENT);
   is(cmd.commandNumber, 0x01);
-  is(cmd.commandQualifier, icc.STK_TIMER_GET_CURRENT_VALUE);
-  is(cmd.options.timerAction, icc.STK_TIMER_GET_CURRENT_VALUE);
+  is(cmd.commandQualifier, iccManager.STK_TIMER_GET_CURRENT_VALUE);
+  is(cmd.options.timerAction, iccManager.STK_TIMER_GET_CURRENT_VALUE);
   is(cmd.options.timerId, 0x08);
 
   runNextTest();
 }
 
 let tests = [
   {command: "d009810301260082028182",
    func: testLocalInfoLocation},
--- a/dom/icc/tests/marionette/test_stk_refresh.js
+++ b/dom/icc/tests/marionette/test_stk_refresh.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testRefresh(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_REFRESH, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_REFRESH, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
 
   runNextTest();
 }
 
 let tests = [
   {command: "d0108103010101820281829205013f002fe2",
    func: testRefresh,
--- a/dom/icc/tests/marionette/test_stk_select_item.js
+++ b/dom/icc/tests/marionette/test_stk_select_item.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSelectItem(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SELECT_ITEM, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SELECT_ITEM, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.title, expect.title, expect.name);
   for (let index in command.options.items) {
     is(command.options.items[index].identifier, expect.items[index].identifier, expect.name);
     is(command.options.items[index].text, expect.items[index].text, expect.name);
   }
 
   runNextTest();
--- a/dom/icc/tests/marionette/test_stk_send_dtmf.js
+++ b/dom/icc/tests/marionette/test_stk_send_dtmf.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSendDTMF(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SEND_DTMF, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SEND_DTMF, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   if (command.options.text) {
     is(command.options.text, expect.text, expect.name);
   }
 
   runNextTest();
 }
 
--- a/dom/icc/tests/marionette/test_stk_send_sms.js
+++ b/dom/icc/tests/marionette/test_stk_send_sms.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSendSMS(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SEND_SMS, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SEND_SMS, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   if (command.options.text) {
     is(command.options.text, expect.title, expect.name);
   }
 
   runNextTest();
 }
 
--- a/dom/icc/tests/marionette/test_stk_send_ss.js
+++ b/dom/icc/tests/marionette/test_stk_send_ss.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSendSS(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SEND_SS, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SEND_SS, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   if (command.options.text) {
     is(command.options.text, expect.title, expect.name);
   }
 
   runNextTest();
 }
 
--- a/dom/icc/tests/marionette/test_stk_send_ussd.js
+++ b/dom/icc/tests/marionette/test_stk_send_ussd.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSendUSSD(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SEND_USSD, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SEND_USSD, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   if (command.options.text) {
     is(command.options.text, expect.title, expect.name);
   }
 
   runNextTest();
 }
 
--- a/dom/icc/tests/marionette/test_stk_setup_call.js
+++ b/dom/icc/tests/marionette/test_stk_setup_call.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSetupCall(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SET_UP_CALL, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SET_UP_CALL, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.confirmMessage, expect.confirmMessage, expect.name);
   is(command.options.address, expect.address, expect.name);
   is(command.options.callMessage, expect.callMessage, expect.name);
 
   let duration = command.options.duration;
   if (duration) {
     is(duration.timeUnit, expect.duration.timeUnit, expect.name);
@@ -63,17 +63,17 @@ let tests = [
             confirmMessage: "Called party",
             address: "+012340123456,1,2"}},
   {command: "d02281030110018202818385084475726174696f6e8609911032042143651c2c8402010a",
    func: testSetupCall,
    expect: {name: "setup_call_cmd_8",
             commandQualifier: 0x01,
             confirmMessage: "Duration",
             address: "+012340123456,1,2",
-            duration: {timeUnit: icc.STK_TIME_UNIT_SECOND,
+            duration: {timeUnit: iccManager.STK_TIME_UNIT_SECOND,
                        timeInterval: 0x0A}}},
   {command: "d028810301100082028183850c434f4e4649524d4154494f4e8609911032042143651c2c850443414c4c",
    func: testSetupCall,
    expect: {name: "setup_call_cmd_9",
             commandQualifier: 0x00,
             confirmMessage: "CONFIRMATION",
             callMessage: "CALL",
             address: "+012340123456,1,2"}},
@@ -331,14 +331,14 @@ let tests = [
             address: "012340123456,1,2",
             callMessage: "Message"}},
   {command: "d02281030110008202818385084E6F7420627573798609911032042143651C2C8402010A",
    func: testSetupCall,
    expect: {name: "setup_call_cmd_47",
             commandQualifier: 0x00,
              confirmMessage: "Not busy",
              address: "+012340123456,1,2",
-            duration: {timeUnit: icc.STK_TIME_UNIT_SECOND,
+            duration: {timeUnit: iccManager.STK_TIME_UNIT_SECOND,
                        timeInterval: 0x0A}}},
 
 ];
 
 runNextTest();
--- a/dom/icc/tests/marionette/test_stk_setup_event_list.js
+++ b/dom/icc/tests/marionette/test_stk_setup_event_list.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSetupEventList(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SET_UP_EVENT_LIST, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SET_UP_EVENT_LIST, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   for (let index in command.options.eventList) {
     is(command.options.eventList[index], expect.eventList[index], expect.name);
   }
 
   runNextTest();
 }
 
--- a/dom/icc/tests/marionette/test_stk_setup_idle_mode_text.js
+++ b/dom/icc/tests/marionette/test_stk_setup_idle_mode_text.js
@@ -1,16 +1,16 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSetupIdleModeText(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SET_UP_IDLE_MODE_TEXT, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SET_UP_IDLE_MODE_TEXT, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.text, expect.text, expect.name);
 
   runNextTest();
 }
 
 let tests = [
   {command: "d01a8103012800820281828d0f0449646c65204d6f64652054657874",
--- a/dom/icc/tests/marionette/test_stk_setup_menu.js
+++ b/dom/icc/tests/marionette/test_stk_setup_menu.js
@@ -1,40 +1,40 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 MARIONETTE_HEAD_JS = "stk_helper.js";
 
 function testSetupMenu(command, expect) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SET_UP_MENU, expect.name);
+  is(command.typeOfCommand, iccManager.STK_CMD_SET_UP_MENU, expect.name);
   is(command.commandQualifier, expect.commandQualifier, expect.name);
   is(command.options.title, expect.title, expect.name);
   for (let index in command.options.items) {
     is(command.options.items[index].identifier, expect.items[index].identifier, expect.name);
     is(command.options.items[index].text, expect.items[index].text, expect.name);
   }
 
   runNextTest();
 }
 
 function isFirstMenuItemNull(command) {
   return (command.options.items.length == 1 && !(command.options.items[0]));
 }
 
 function testInitialSetupMenu(command) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SET_UP_MENU);
+  is(command.typeOfCommand, iccManager.STK_CMD_SET_UP_MENU);
   is(isFirstMenuItemNull(command), false);
 
   runNextTest();
 }
 function testRemoveSetupMenu(command) {
   log("STK CMD " + JSON.stringify(command));
-  is(command.typeOfCommand, icc.STK_CMD_SET_UP_MENU);
+  is(command.typeOfCommand, iccManager.STK_CMD_SET_UP_MENU);
   is(isFirstMenuItemNull(command), true);
 
   runNextTest();
 }
 
 let tests = [
   {command: "d03b810301250082028182850c546f6f6c6b6974204d656e758f07014974656d20318f07024974656d20328f07034974656d20338f07044974656d2034",
    func: testSetupMenu,
--- a/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
+++ b/dom/network/interfaces/nsIDOMNetworkStatsManager.idl
@@ -15,17 +15,17 @@ interface nsIDOMMozNetworkStatsInterface
   readonly attribute long type;
 
   /**
    * Id value is '0' for wifi or the iccid for mobile (SIM).
    */
   readonly attribute DOMString id;
 };
 
-[scriptable,  uuid(5fbdcae6-a2cd-47b3-929f-83ac75bd4881)]
+[scriptable, uuid(5f033d31-c9a2-4e2d-83aa-6a807f1e0c11)]
 interface nsIDOMMozNetworkStatsManager : nsISupports
 {
   /**
    * Constants for known interface types.
    */
   const long WIFI = 0;
   const long MOBILE = 1;
 
@@ -48,19 +48,19 @@ interface nsIDOMMozNetworkStatsManager :
   nsIDOMDOMRequest clearStats(in nsIDOMMozNetworkStatsInterface network);
 
   /**
    * Remove all stats in the database.
    */
   nsIDOMDOMRequest clearAllStats();
 
   /**
-   * Return currently available networks.
+   * Return available networks that used to be saved in the database.
    */
-  readonly attribute jsval availableNetworks; // array of nsIDOMMozNetworkStatsInterface.
+  nsIDOMDOMRequest getAvailableNetworks(); // array of nsIDOMMozNetworkStatsInterface.
 
   /**
    * Minimum time in milliseconds between samples stored in the database.
    */
   readonly attribute long sampleRate;
 
   /**
    * Time in milliseconds recorded by the API until present time. All samples
--- a/dom/network/src/NetworkStatsDB.jsm
+++ b/dom/network/src/NetworkStatsDB.jsm
@@ -469,16 +469,51 @@ NetworkStatsDB.prototype = {
 
     while (aEnd > aData[aData.length - 1].date.getTime()) {
       aData.push({ rxBytes: undefined,
                    txBytes: undefined,
                    date: new Date(aData[aData.length - 1].date.getTime() + SAMPLE_RATE) });
     }
   },
 
+  getAvailableNetworks: function getAvailableNetworks(aResultCb) {
+    this.dbNewTxn("readonly", function(aTxn, aStore) {
+      if (!aTxn.result) {
+        aTxn.result = [];
+      }
+
+      let request = aStore.index("network").openKeyCursor(null, "nextunique");
+      request.onsuccess = function onsuccess(event) {
+        let cursor = event.target.result;
+        if (cursor) {
+          aTxn.result.push({ id: cursor.key[0],
+                             type: cursor.key[1] });
+          cursor.continue();
+          return;
+        }
+      };
+    }, aResultCb);
+  },
+
+  isNetworkAvailable: function isNetworkAvailable(aNetwork, aResultCb) {
+    this.dbNewTxn("readonly", function(aTxn, aStore) {
+      if (!aTxn.result) {
+        aTxn.result = false;
+      }
+
+      var network = [aNetwork.id, aNetwork.type];
+      let request = aStore.index("network").openKeyCursor(IDBKeyRange.only(network));
+      request.onsuccess = function onsuccess(event) {
+        if (event.target.result) {
+          aTxn.result = true;
+        }
+      };
+    }, aResultCb);
+  },
+
   get sampleRate () {
     return SAMPLE_RATE;
   },
 
   get maxStorageSamples () {
     return VALUES_MAX_LENGTH;
   },
 
--- a/dom/network/src/NetworkStatsManager.js
+++ b/dom/network/src/NetworkStatsManager.js
@@ -123,17 +123,17 @@ NetworkStats.prototype = {
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStats,
                                           nsIDOMMozNetworkStatsData,
                                           nsIDOMMozNetworkStatsInterface])
 }
 
 // NetworkStatsManager
 
 const NETWORKSTATSMANAGER_CONTRACTID = "@mozilla.org/networkStatsManager;1";
-const NETWORKSTATSMANAGER_CID        = Components.ID("{5fbdcae6-a2cd-47b3-929f-83ac75bd4881}");
+const NETWORKSTATSMANAGER_CID        = Components.ID("{5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}");
 const nsIDOMMozNetworkStatsManager   = Components.interfaces.nsIDOMMozNetworkStatsManager;
 
 function NetworkStatsManager() {
   if (DEBUG) {
     debug("Constructor");
   }
 }
 
@@ -184,26 +184,23 @@ NetworkStatsManager.prototype = {
     this.checkPrivileges();
 
     let request = this.createRequest();
     cpmm.sendAsyncMessage("NetworkStats:ClearAll",
                           {id: this.getRequestId(request)});
     return request;
   },
 
-  get availableNetworks() {
+  getAvailableNetworks: function getAvailableNetworks() {
     this.checkPrivileges();
 
-    let result = ObjectWrapper.wrap(cpmm.sendSyncMessage("NetworkStats:Networks")[0], this._window);
-    let networks = this.data = Cu.createArrayIn(this._window);
-    for (let i = 0; i < result.length; i++) {
-      networks.push(new NetworkStatsInterface(result[i]));
-    }
-
-    return networks;
+    let request = this.createRequest();
+    cpmm.sendAsyncMessage("NetworkStats:GetAvailableNetworks",
+                          { id: this.getRequestId(request) });
+    return request;
   },
 
   get sampleRate() {
     this.checkPrivileges();
     return cpmm.sendSyncMessage("NetworkStats:SampleRate")[0];
   },
 
   get maxStorageAge() {
@@ -234,16 +231,30 @@ NetworkStatsManager.prototype = {
 
         let result = new NetworkStats(this._window, msg.result);
         if (DEBUG) {
           debug("result: " + JSON.stringify(result));
         }
         Services.DOMRequest.fireSuccess(req, result);
         break;
 
+      case "NetworkStats:GetAvailableNetworks:Return":
+        if (msg.error) {
+          Services.DOMRequest.fireError(req, msg.error);
+          return;
+        }
+
+        let networks = Cu.createArrayIn(this._window);
+        for (let i = 0; i < msg.result.length; i++) {
+          networks.push(new NetworkStatsInterface(msg.result[i]));
+        }
+
+        Services.DOMRequest.fireSuccess(req, networks);
+        break;
+
       case "NetworkStats:Clear:Return":
       case "NetworkStats:ClearAll:Return":
         if (msg.error) {
           Services.DOMRequest.fireError(req, msg.error);
           return;
         }
 
         Services.DOMRequest.fireSuccess(req, true);
@@ -275,31 +286,33 @@ NetworkStatsManager.prototype = {
       debug("has privileges: " + this.hasPrivileges);
     }
 
     if (!this.hasPrivileges) {
       return null;
     }
 
     this.initDOMRequestHelper(aWindow, ["NetworkStats:Get:Return",
+                                        "NetworkStats:GetAvailableNetworks:Return",
                                         "NetworkStats:Clear:Return",
                                         "NetworkStats:ClearAll:Return"]);
   },
 
   // Called from DOMRequestIpcHelper
   uninit: function uninit() {
     if (DEBUG) {
       debug("uninit call");
     }
   },
 
   classID : NETWORKSTATSMANAGER_CID,
   QueryInterface : XPCOMUtils.generateQI([nsIDOMMozNetworkStatsManager,
                                          Ci.nsIDOMGlobalPropertyInitializer,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
   classInfo : XPCOMUtils.generateCI({classID: NETWORKSTATSMANAGER_CID,
                                      contractID: NETWORKSTATSMANAGER_CONTRACTID,
                                      classDescription: "NetworkStatsManager",
                                      interfaces: [nsIDOMMozNetworkStatsManager],
                                      flags: nsIClassInfo.DOM_OBJECT})
 }
 
--- a/dom/network/src/NetworkStatsManager.manifest
+++ b/dom/network/src/NetworkStatsManager.manifest
@@ -2,11 +2,11 @@ component {3b16fe17-5583-483a-b486-b64a3
 contract @mozilla.org/networkStatsdata;1 {3b16fe17-5583-483a-b486-b64a3243221c}
 
 component {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe} NetworkStatsManager.js
 contract @mozilla.org/networkStats;1 {b6fc4b14-628d-4c99-bf4e-e4ed56916cbe}
 
 component {f540615b-d803-43ff-8200-2a9d145a5645} NetworkStatsManager.js
 contract @mozilla.org/networkstatsinterface;1 {f540615b-d803-43ff-8200-2a9d145a5645}
 
-component {5fbdcae6-a2cd-47b3-929f-83ac75bd4881} NetworkStatsManager.js
-contract @mozilla.org/networkStatsManager;1 {5fbdcae6-a2cd-47b3-929f-83ac75bd4881}
+component {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11} NetworkStatsManager.js
+contract @mozilla.org/networkStatsManager;1 {5f033d31-c9a2-4e2d-83aa-6a807f1e0c11}
 category JavaScript-navigator-property mozNetworkStats @mozilla.org/networkStatsManager;1
--- a/dom/network/src/NetworkStatsService.jsm
+++ b/dom/network/src/NetworkStatsService.jsm
@@ -83,17 +83,17 @@ this.NetworkStatsService = {
     let netId = this.getNetworkId('0', NET_TYPE_WIFI);
     this._networks[netId] = { network:       { id: '0',
                                                type: NET_TYPE_WIFI },
                               interfaceName: null };
 
     this.messages = ["NetworkStats:Get",
                      "NetworkStats:Clear",
                      "NetworkStats:ClearAll",
-                     "NetworkStats:Networks",
+                     "NetworkStats:GetAvailableNetworks",
                      "NetworkStats:SampleRate",
                      "NetworkStats:MaxStorageAge"];
 
     this.messages.forEach(function(aMsgName) {
       ppmm.addMessageListener(aMsgName, this);
     }, this);
 
     this._db = new NetworkStatsDB();
@@ -125,18 +125,19 @@ this.NetworkStatsService = {
         this.getSamples(mm, msg);
         break;
       case "NetworkStats:Clear":
         this.clearInterfaceStats(mm, msg);
         break;
       case "NetworkStats:ClearAll":
         this.clearDB(mm, msg);
         break;
-      case "NetworkStats:Networks":
-        return this.availableNetworks();
+      case "NetworkStats:GetAvailableNetworks":
+        this.getAvailableNetworks(mm, msg);
+        break;
       case "NetworkStats:SampleRate":
         // This message is sync.
         return this._db.sampleRate;
       case "NetworkStats:MaxStorageAge":
         // This message is sync.
         return this._db.maxStorageSamples * this._db.sampleRate;
     }
   },
@@ -153,16 +154,17 @@ this.NetworkStatsService = {
         let network = aSubject.QueryInterface(Ci.nsINetworkInterface);
         debug("Network " + network.name + " of type " + network.type + " status change");
 
         let netId = this.convertNetworkInterface(network);
         if (!netId) {
           break;
         }
 
+        debug("NetId: " + netId);
         this.updateStats(netId);
         break;
       case "xpcom-shutdown":
         debug("Service shutdown");
 
         this.messages.forEach(function(aMsgName) {
           ppmm.removeMessageListener(aMsgName, this);
         }, this);
@@ -221,70 +223,87 @@ this.NetworkStatsService = {
     this._networks[netId].interfaceName = aNetwork.name;
     return netId;
   },
 
   getNetworkId: function getNetworkId(aIccId, aNetworkType) {
     return aIccId + '' + aNetworkType;
   },
 
-  availableNetworks: function availableNetworks() {
-    let result = [];
-    for (let netId in this._networks) {
-      result.push(this._networks[netId].network);
-    }
-
-    return result;
+  getAvailableNetworks: function getAvailableNetworks(mm, msg) {
+    this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
+      mm.sendAsyncMessage("NetworkStats:GetAvailableNetworks:Return",
+                          { id: msg.id, error: aError, result: aResult });
+    });
   },
 
   /*
    * Function called from manager to get stats from database.
    * In order to return updated stats, first is performed a call to
    * updateAllStats function, which will get last stats from netd
    * and update the database.
    * Then, depending on the request (stats per appId or total stats)
    * it retrieve them from database and return to the manager.
    */
   getSamples: function getSamples(mm, msg) {
     let self = this;
     let network = msg.network;
     let netId = this.getNetworkId(network.id, network.type);
 
-    if (!this._networks[netId]) {
-      mm.sendAsyncMessage("NetworkStats:Get:Return",
-                          { id: msg.id, error: "Invalid connectionType", result: null });
-      return;
-    }
-
     let appId = 0;
     let manifestURL = msg.manifestURL;
     if (manifestURL) {
       appId = appsService.getAppLocalIdByManifestURL(manifestURL);
 
       if (!appId) {
         mm.sendAsyncMessage("NetworkStats:Get:Return",
                             { id: msg.id, error: "Invalid manifestURL", result: null });
         return;
       }
     }
 
     let start = new Date(msg.start);
     let end = new Date(msg.end);
 
-    this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
-      debug("getstats for network " + network.id + " of type " + network.type);
-      debug("appId: " + appId + " from manifestURL: " + manifestURL);
+    // Check if the network is currently active. If yes, we need to update
+    // the cached stats first before retrieving stats from the DB.
+    if (this._networks[netId]) {
+      this.updateStats(netId, function onStatsUpdated(aResult, aMessage) {
+        debug("getstats for network " + network.id + " of type " + network.type);
+        debug("appId: " + appId + " from manifestURL: " + manifestURL);
 
-      this.updateCachedAppStats(function onAppStatsUpdated(aResult, aMessage) {
+        self.updateCachedAppStats(function onAppStatsUpdated(aResult, aMessage) {
+          self._db.find(function onStatsFound(aError, aResult) {
+            mm.sendAsyncMessage("NetworkStats:Get:Return",
+                                { id: msg.id, error: aError, result: aResult });
+          }, network, start, end, appId, manifestURL);
+        });
+      });
+      return;
+    }
+
+    // Check if the network is available in the DB. If yes, we also
+    // retrieve the stats for it from the DB.
+    this._db.isNetworkAvailable(network, function(aError, aResult) {
+      if (aResult) {
+        // If network is not active, there is no need to update stats.
         self._db.find(function onStatsFound(aError, aResult) {
           mm.sendAsyncMessage("NetworkStats:Get:Return",
                               { id: msg.id, error: aError, result: aResult });
         }, network, start, end, appId, manifestURL);
-      });
-    }.bind(this));
+        return;
+      }
+
+      if (!aError) {
+        aError = "Invalid connectionType";
+      }
+
+      mm.sendAsyncMessage("NetworkStats:Get:Return",
+                          { id: msg.id, error: aError, result: null });
+    });
   },
 
   clearInterfaceStats: function clearInterfaceStats(mm, msg) {
     let network = msg.network;
     let netId = this.getNetworkId(network.id, network.type);
 
     debug("clear stats for network " + network.id + " of type " + network.type);
 
@@ -296,20 +315,29 @@ this.NetworkStatsService = {
 
     this._db.clearInterfaceStats(network, function onDBCleared(aError, aResult) {
         mm.sendAsyncMessage("NetworkStats:Clear:Return",
                             { id: msg.id, error: aError, result: aResult });
     });
   },
 
   clearDB: function clearDB(mm, msg) {
-    let networks = this.availableNetworks();
-    this._db.clearStats(networks, function onDBCleared(aError, aResult) {
-      mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
-                          { id: msg.id, error: aError, result: aResult });
+    let self = this;
+    this._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
+      if (aError) {
+        mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
+                            { id: msg.id, error: aError, result: aResult });
+        return;
+      }
+
+      let networks = aResult;
+      self._db.clearStats(networks, function onDBCleared(aError, aResult) {
+        mm.sendAsyncMessage("NetworkStats:ClearAll:Return",
+                            { id: msg.id, error: aError, result: aResult });
+      });
     });
   },
 
   updateAllStats: function updateAllStats(aCallback) {
     // Update |cachedAppStats|.
     this.updateCachedAppStats();
 
     let elements = [];
--- a/dom/network/tests/test_networkstats_basics.html
+++ b/dom/network/tests/test_networkstats_basics.html
@@ -14,21 +14,16 @@
 
 function test() {
   ok('mozNetworkStats' in navigator, "navigator.mozMozNetworkStats should exist");
   ok(navigator.mozNetworkStats, "navigator.mozNetworkStats returns an object");
 
   netStats = navigator.mozNetworkStats;
 
   // Test IDL attributes
-  ok('availableNetworks' in netStats,
-   "availableNetworks should be a NetworkStats attribute");
-  ok(Array.isArray(netStats.availableNetworks) && netStats.availableNetworks.length > 0,
-   "availableNetworks is an array not empty.");
-
   ok('sampleRate' in netStats,
    "sampleRate should be a NetworkStats attribute");
   ok(netStats.sampleRate > 0,
    "sampleRate is greater than 0.");
 
   ok('maxStorageAge' in netStats,
    "maxStorageAge should be a NetworkStats attribute");
   ok(netStats.maxStorageAge > 0,
@@ -66,46 +61,66 @@ function compareNetworks(networkA, netwo
 }
 
 var req;
 var index = -1;
 var netStats = null;
 
 var steps = [
   function () {
+    // Test getAvailableNetworks
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      ok(true, "getAvailableNetworks request ok");
+      ok(Array.isArray(req.result) && req.result.length > 0,
+         "getAvailableNetworks returns an array not empty");
+      next();
+    };
+    req.onerror = function () {
+      ok(false, "getAvailableNetworks failure!");
+    }
+  },
+  function () {
     // Test clearAllStats
     req = netStats.clearAllStats();
     req.onsuccess = function () {
       ok(true, "clearAllStats deleted the database");
       next();
     };
     req.onerror = function () {
       ok(false, "clearAllStats deleted the database");
     }
   },
   function () {
     // Check if getSamples launch exception when start is greather than end
 
     // Prepare get params
-    var network = netStats.availableNetworks[0];
-    // Get dates
-    var endDate = new Date();
-    var startDate = new Date(endDate.getTime() + 1000);
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      var network = req.result[0];
+
+      // Get dates
+      var endDate = new Date();
+      var startDate = new Date(endDate.getTime() + 1000);
 
-    try {
-      netStats.getSamples(network, startDate, endDate);
-    } catch(ex) {
-      ok(true, "getSamples launch exception when start is greater than end");
+      try {
+        netStats.getSamples(network, startDate, endDate);
+      } catch(ex) {
+        ok(true, "getSamples launch exception when start is greater than end");
+        next();
+        return;
+      }
+
+      ok(false, "getSamples launch exception when start is greater than end");
       next();
       return;
+    };
+    req.onerror = function () {
+      ok(false, "Error getting networks!");
     }
-
-    ok(false, "getSamples launch exception when start is greater than end");
-    next();
-    return;
   },
   function () {
     // Test if call getSamples with network of type different than
     // nsIDOMMozNetworkStatsInterface launch an exception
 
     // Prepare get params
     var network = "wifi";
     var endDate = new Date();
@@ -122,129 +137,161 @@ var steps = [
 
     ok(false, "getSamples launch exception if network is not " +
               "a nsIDOMMozNetworkStatsInterface");
   },
   function () {
     // Test if call getSamples with start parameter type different than Date launch an exception
 
     // Prepare get params
-    var network = netStats.availableNetworks[0];
-    var endDate = new Date();
-    var startDate = new Date(endDate.getTime() - 1000);
-    startDate = startDate.toString();
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      var network = req.result[0];
+
+      var endDate = new Date();
+      var startDate = new Date(endDate.getTime() - 1000);
+      startDate = startDate.toString();
 
-    try {
-      netStats.getSamples(network, startDate, endDate);
-    } catch(ex) {
-      ok(true, "getSamples launch exception when start param is not a Date");
-      next();
-      return;
-    }
+      try {
+        netStats.getSamples(network, startDate, endDate);
+      } catch(ex) {
+        ok(true, "getSamples launch exception when start param is not a Date");
+        next();
+        return;
+      }
 
-    ok(false, "getSamples launch exception when start param is not a Date");
+      ok(false, "getSamples launch exception when start param is not a Date");
+    };
+    req.onerror = function () {
+      ok(false, "Error getting networks!");
+    };
   },
   function () {
     // Test if call getSamples with end parameter type different than Date launch an exception
 
     // Prepare get params
-    var network = netStats.availableNetworks[0];
-    var endDate = new Date();
-    var startDate = new Date(endDate.getTime() - 1000);
-    endDate = startDate.toString();
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      var network = req.result[0];
+
+      var endDate = new Date();
+      var startDate = new Date(endDate.getTime() - 1000);
+      endDate = startDate.toString();
 
-    try {
-      netStats.getSamples(network, startDate, endDate);
-    } catch(ex) {
-      ok(true, "getSamples launch exception when end param is not a Date");
-      next();
-      return;
-    }
+      try {
+        netStats.getSamples(network, startDate, endDate);
+      } catch(ex) {
+        ok(true, "getSamples launch exception when end param is not a Date");
+        next();
+        return;
+      }
 
-    ok(false, "getSamples launch exception when end param is not a Date");
+      ok(false, "getSamples launch exception when end param is not a Date");
+    };
+    req.onerror = function () {
+      ok(false, "Error getting networks!");
+    };
   },
   function () {
     ok(true, "Get stats for a network and dates adapted to samplerate");
     // Prepare get params
-    var network = netStats.availableNetworks[0];
-    var diff = 2;
-    // Get samplerate in millis
-    var sampleRate = netStats.sampleRate;
-    // Get date with samplerate's precision
-    var offset = new Date().getTimezoneOffset() * 60 * 1000;
-    var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
-                           * sampleRate + offset);
-    var startDate = new Date(endDate.getTime() - (sampleRate * diff));
-    // Calculate the number of samples that should be returned based on the
-    // the samplerate and including final and initial samples.
-    var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      var network = req.result[0];
+      var diff = 2;
+      // Get samplerate in millis
+      var sampleRate = netStats.sampleRate;
+      // Get date with samplerate's precision
+      var offset = new Date().getTimezoneOffset() * 60 * 1000;
+      var endDate = new Date(Math.floor((new Date().getTime() - offset) / sampleRate)
+                             * sampleRate + offset);
+      var startDate = new Date(endDate.getTime() - (sampleRate * diff));
+      // Calculate the number of samples that should be returned based on the
+      // the samplerate and including final and initial samples.
+      var samples = (endDate.getTime() - startDate.getTime()) / sampleRate + 1;
 
-    // Launch request
-    req = netStats.getSamples(network, startDate, endDate);
-    req.onsuccess = function () {
-      ok(true, "Get system stats request ok");
-      ok(req.result.manifestURL == null, "manifestURL should be null");
-      ok(compareNetworks(req.result.network, network), "networks should be equals");
-      ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
-      ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
-      var data = req.result.data;
-      ok(Array.isArray(data) && data.length == samples,
-         "data is an array of length " + samples);
-      checkDataDates(data, startDate, endDate, sampleRate);
-      next();
+      // Launch request
+      req = netStats.getSamples(network, startDate, endDate);
+      req.onsuccess = function () {
+        ok(true, "Get system stats request ok");
+        ok(req.result.manifestURL == null, "manifestURL should be null");
+        ok(compareNetworks(req.result.network, network), "networks should be equals");
+        ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
+        ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
+        var data = req.result.data;
+        ok(Array.isArray(data) && data.length == samples,
+           "data is an array of length " + samples);
+        checkDataDates(data, startDate, endDate, sampleRate);
+        next();
+      };
+      req.onerror = function () {
+        ok(false, "Get stats failure!");
+      }
     };
     req.onerror = function () {
-      ok(false, "Get stats failure!");
-    }
+      ok(false, "Error getting networks!");
+    };
   },
   function () {
     ok(true, "Get system stats for a network and dates not adapted to samplerate");
     // Prepare get params
-    var network = netStats.availableNetworks[0];
-    var diff = 2;
-    // Get samplerate in millis
-    var sampleRate = netStats.sampleRate;
-    var endDate = new Date();
-    var startDate = new Date(endDate.getTime() - (sampleRate * diff));
-    // Calculate the number of samples that should be returned based on the
-    // the samplerate, including final and initial samples and taking into
-    // account that these will be filtered according to precision.
-    var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
-                   Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      var network = req.result[0];
+      var diff = 2;
+      // Get samplerate in millis
+      var sampleRate = netStats.sampleRate;
+      var endDate = new Date();
+      var startDate = new Date(endDate.getTime() - (sampleRate * diff));
+      // Calculate the number of samples that should be returned based on the
+      // the samplerate, including final and initial samples and taking into
+      // account that these will be filtered according to precision.
+      var samples = (Math.floor(endDate.getTime() / (sampleRate)) * sampleRate -
+                     Math.floor(startDate.getTime() / (sampleRate)) * sampleRate) / sampleRate + 1;
 
-    // Launch request
-    req = netStats.getSamples(network, startDate, endDate);
-    req.onsuccess = function () {
-      ok(true, "Get stats request ok");
-      ok(req.result.manifestURL == null, "manifestURL should be null");
-      ok(compareNetworks(req.result.network, network), "networks should be equals");
-      ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
-      ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
-      var data = req.result.data;
-      ok(Array.isArray(data) && data.length == samples,
-         "data is an array of length " + samples);
-      checkDataDates(data, startDate, endDate, sampleRate);
-      next();
+      // Launch request
+      req = netStats.getSamples(network, startDate, endDate);
+      req.onsuccess = function () {
+        ok(true, "Get stats request ok");
+        ok(req.result.manifestURL == null, "manifestURL should be null");
+        ok(compareNetworks(req.result.network, network), "networks should be equals");
+        ok(req.result.start.getTime() == startDate.getTime(), "starts should be equals");
+        ok(req.result.end.getTime() == endDate.getTime(), "ends should be equals");
+        var data = req.result.data;
+        ok(Array.isArray(data) && data.length == samples,
+           "data is an array of length " + samples);
+        checkDataDates(data, startDate, endDate, sampleRate);
+        next();
+      };
+      req.onerror = function () {
+        ok(false, "Get stats failure!");
+      }
     };
     req.onerror = function () {
-      ok(false, "Get stats failure!");
-    }
+      ok(false, "Error getting networks!");
+    };
   },
   function () {
     // Test clearStats
-    var network = netStats.availableNetworks[0];
+    req = netStats.getAvailableNetworks();
+    req.onsuccess = function () {
+      var network = req.result[0];
 
-    req = netStats.clearStats(network);
-    req.onsuccess = function () {
-      ok(true, "clearStats deleted the database");
-      next();
+      req = netStats.clearStats(network);
+      req.onsuccess = function () {
+        ok(true, "clearStats deleted the database");
+        next();
+      };
+      req.onerror = function () {
+        ok(false, "clearStats deleted the database");
+      }
     };
     req.onerror = function () {
-      ok(false, "clearStats deleted the database");
-    }
+      ok(false, "Error getting networks!");
+    };
   },
   function () {
     ok(true, "all done!\n");
     SpecialPowers.removePermission("networkstats-manage", document);
     SimpleTest.finish();
     return;
   }
 ];
--- a/dom/network/tests/test_networkstats_enabled_no_perm.html
+++ b/dom/network/tests/test_networkstats_enabled_no_perm.html
@@ -7,29 +7,29 @@
 </head>
 <body>
 <p id="display"></p>
 <div id="content" style="display: none">
 </div>
 <pre id="test">
 <script type="application/javascript">
 
-// Test to ensure NetworkStats is enabled but mozNetworkStats.availableNetworks
+// Test to ensure NetworkStats is enabled but mozNetworkStats.getAvailableNetworks
 // does not work in content.
 
 SpecialPowers.setBoolPref("dom.mozNetworkStats.enabled", true);
 SpecialPowers.removePermission("networkstats-manage", document);
 
 ok('mozNetworkStats' in navigator, "navigator.mozNetworkStats should be accessible if dom.mozNetworkStats.enabled is true");
 
 var error;
 try {
-  navigator.mozNetworkStats.availableNetworks;
-  ok(false, "Accessing navigator.mozNetworkStats.availableNetworks should have thrown!");
+  navigator.mozNetworkStats.getAvailableNetworks;
+  ok(false, "Accessing navigator.mozNetworkStats.getAvailableNetworks should have thrown!");
 } catch (ex) {
   error = ex;
 }
-ok(error, "Got an exception accessing navigator.mozNetworkStats.availableNetworks");
+ok(error, "Got an exception accessing navigator.mozNetworkStats.getAvailableNetworks");
 
 </script>
 </pre>
 </body>
 </html>
--- a/dom/network/tests/unit_stats/test_networkstats_db.js
+++ b/dom/network/tests/unit_stats/test_networkstats_db.js
@@ -2,16 +2,22 @@
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
 Cu.import("resource://gre/modules/NetworkStatsDB.jsm");
 
 const netStatsDb = new NetworkStatsDB();
 
+function clearWholeDB(callback) {
+  netStatsDb.dbNewTxn("readwrite", function(aTxn, aStore) {
+    aStore.delete();
+  }, callback);
+}
+
 function filterTimestamp(date) {
   var sampleRate = netStatsDb.sampleRate;
   var offset = date.getTimezoneOffset() * 60 * 1000;
   return Math.floor((date.getTime() - offset) / sampleRate) * sampleRate;
 }
 
 function getNetworks() {
   return [{ id: '0', type: Ci.nsIDOMMozNetworkStatsManager.WIFI },
@@ -569,10 +575,14 @@ add_test(function test_saveMultipleAppSt
         netStatsDb.saveStats(cached[keys[index]], callback);
     });
   });
 });
 
 function run_test() {
   do_get_profile();
 
-  run_next_test();
+  // Clear whole database to avoid start tests with unknown state
+  // due to previous tests.
+  clearWholeDB(function(){
+    run_next_test();
+  });
 }
--- a/dom/network/tests/unit_stats/test_networkstats_service.js
+++ b/dom/network/tests/unit_stats/test_networkstats_service.js
@@ -1,55 +1,76 @@
 /* Any copyright is dedicated to the Public Domain.
    http://creativecommons.org/publicdomain/zero/1.0/ */
 
 const {classes: Cc, interfaces: Ci, utils: Cu, results: Cr} = Components;
 
+function getNetworks(callback) {
+  NetworkStatsService._db.getAvailableNetworks(function onGetNetworks(aError, aResult) {
+    callback(aError, aResult);
+  });
+}
+
 add_test(function test_clearDB() {
-  var networks = NetworkStatsService.availableNetworks();
-  NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) {
-    do_check_eq(result, null);
-    run_next_test();
+  getNetworks(function onGetNetworks(error, result) {
+    do_check_eq(error, null);
+    var networks = result;
+    NetworkStatsService._db.clearStats(networks, function onDBCleared(error, result) {
+      do_check_eq(result, null);
+      run_next_test();
+    });
   });
 });
 
-function getNetworkId() {
-  var network = (NetworkStatsService.availableNetworks())[0];
-  return NetworkStatsService.getNetworkId(network.id, network.type);
+function getNetworkId(callback) {
+  getNetworks(function onGetNetworks(error, result) {
+    do_check_eq(error, null);
+    var netId = NetworkStatsService.getNetworkId(result[0].id, result[0].type);
+    callback(null, netId);
+  });
 }
 
 add_test(function test_networkStatsAvailable_ok() {
-  var netId = getNetworkId();
-  NetworkStatsService.networkStatsAvailable(function (success, msg) {
-    do_check_eq(success, true);
-    run_next_test();
-  }, netId, true, 1234, 4321, new Date());
+  getNetworkId(function onGetId(error, result) {
+    do_check_eq(error, null);
+    var netId = result;
+    NetworkStatsService.networkStatsAvailable(function (success, msg) {
+      do_check_eq(success, true);
+      run_next_test();
+    }, netId, true, 1234, 4321, new Date());
+  });
 });
 
 add_test(function test_networkStatsAvailable_failure() {
-  var netId = getNetworkId();
-  NetworkStatsService.networkStatsAvailable(function (success, msg) {
-    do_check_eq(success, false);
-    run_next_test();
-  }, netId, false, 1234, 4321, new Date());
+  getNetworkId(function onGetId(error, result) {
+    do_check_eq(error, null);
+    var netId = result;
+    NetworkStatsService.networkStatsAvailable(function (success, msg) {
+      do_check_eq(success, false);
+      run_next_test();
+    }, netId, false, 1234, 4321, new Date());
+  });
 });
 
 add_test(function test_update_invalidNetwork() {
   NetworkStatsService.update(-1, function (success, msg) {
     do_check_eq(success, false);
     do_check_eq(msg, "Invalid network -1");
     run_next_test();
   });
 });
 
 add_test(function test_update() {
-  var netId = getNetworkId();
-  NetworkStatsService.update(netId, function (success, msg) {
-    do_check_eq(success, true);
-    run_next_test();
+  getNetworkId(function onGetId(error, result) {
+    do_check_eq(error, null);
+    var netId = result;
+    NetworkStatsService.update(netId, function (success, msg) {
+      do_check_eq(success, true);
+      run_next_test();
+    });
   });
 });
 
 add_test(function test_updateQueueIndex() {
   NetworkStatsService.updateQueue = [{netId: 0, callbacks: null},
                                      {netId: 1, callbacks: null},
                                      {netId: 2, callbacks: null},
                                      {netId: 3, callbacks: null},
@@ -66,20 +87,23 @@ add_test(function test_updateQueueIndex(
 add_test(function test_updateAllStats() {
   NetworkStatsService.updateAllStats(function(success, msg) {
     do_check_eq(success, true);
     run_next_test();
   });
 });
 
 add_test(function test_updateStats_ok() {
-  var netId = getNetworkId();
-  NetworkStatsService.updateStats(netId, function(success, msg){
-    do_check_eq(success, true);
-    run_next_test();
+  getNetworkId(function onGetId(error, result) {
+    do_check_eq(error, null);
+    var netId = result;
+    NetworkStatsService.updateStats(netId, function(success, msg){
+      do_check_eq(success, true);
+      run_next_test();
+    });
   });
 });
 
 add_test(function test_updateStats_failure() {
   NetworkStatsService.updateStats(-1, function(success, msg){
     do_check_eq(success, false);
     run_next_test();
   });
--- a/dom/payment/Payment.js
+++ b/dom/payment/Payment.js
@@ -25,17 +25,18 @@ XPCOMUtils.defineLazyServiceGetter(this,
 function PaymentContentHelper() {
 };
 
 PaymentContentHelper.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsINavigatorPayment,
                                          Ci.nsIDOMGlobalPropertyInitializer,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
   classID:        PAYMENTCONTENTHELPER_CID,
   classInfo:      XPCOMUtils.generateCI({
     classID: PAYMENTCONTENTHELPER_CID,
     contractID: "@mozilla.org/payment/content-helper;1",
     classDescription: "Payment Content Helper",
     flags: Ci.nsIClassInfo.DOM_OBJECT,
     interfaces: [Ci.nsINavigatorPayment]
   }),
--- a/dom/phonenumberutils/PhoneNumberService.js
+++ b/dom/phonenumberutils/PhoneNumberService.js
@@ -87,12 +87,13 @@ PhoneNumberService.prototype = {
       "PhoneNumberService:FuzzyMatch:Return:OK",
       "PhoneNumberService:FuzzyMatch:Return:KO"
     ]);
   },
 
   classID : Components.ID("{e2768710-eb17-11e2-91e2-0800200c9a66}"),
   contractID : "@mozilla.org/phoneNumberService;1",
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
-                                          Ci.nsISupportsWeakReference]),
+                                          Ci.nsISupportsWeakReference,
+                                          Ci.nsIObserver]),
 }
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([PhoneNumberService]);
--- a/dom/push/src/Push.js
+++ b/dom/push/src/Push.js
@@ -35,17 +35,18 @@ function Push() {
 Push.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   contractID: "@mozilla.org/push/PushManager;1",
 
   classID : PUSH_CID,
 
   QueryInterface : XPCOMUtils.generateQI([Ci.nsIDOMGlobalPropertyInitializer,
-                                          Ci.nsISupportsWeakReference]),
+                                          Ci.nsISupportsWeakReference,
+                                          Ci.nsIObserver]),
 
   init: function(aWindow) {
     // Set debug first so that all debugging actually works.
     // NOTE: We don't add an observer here like in PushService. Flipping the
     // pref will require a reload of the app/page, which seems acceptable.
     gDebuggingEnabled = Services.prefs.getBoolPref("services.push.debug");
     debug("init()");
 
--- a/dom/system/gonk/RILContentHelper.js
+++ b/dom/system/gonk/RILContentHelper.js
@@ -486,17 +486,18 @@ function RILContentHelper() {
 RILContentHelper.prototype = {
   __proto__: DOMRequestIpcHelper.prototype,
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIMobileConnectionProvider,
                                          Ci.nsICellBroadcastProvider,
                                          Ci.nsIVoicemailProvider,
                                          Ci.nsIIccProvider,
                                          Ci.nsIObserver,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
   classID:   RILCONTENTHELPER_CID,
   classInfo: XPCOMUtils.generateCI({classID: RILCONTENTHELPER_CID,
                                     classDescription: "RILContentHelper",
                                     interfaces: [Ci.nsIMobileConnectionProvider,
                                                  Ci.nsICellBroadcastProvider,
                                                  Ci.nsIVoicemailProvider,
                                                  Ci.nsIIccProvider]}),
 
@@ -551,17 +552,17 @@ RILContentHelper.prototype = {
    * We need to consider below cases when update iccInfo:
    * 1. Should clear iccInfo to null if there is no card detected.
    * 2. Need to create corresponding object based on iccType.
    */
   updateIccInfo: function updateIccInfo(clientId, newInfo) {
     let rilContext = this.rilContexts[clientId];
 
     // Card is not detected, clear iccInfo to null.
-    if (!newInfo || !newInfo.iccType) {
+    if (!newInfo || !newInfo.iccType || !newInfo.iccid) {
       if (rilContext.iccInfo) {
         rilContext.iccInfo = null;
         this._deliverEvent(clientId,
                            "_mobileConnectionListeners",
                            "notifyIccChanged",
                            null);
       }
       return;
--- a/dom/system/gonk/RadioInterfaceLayer.js
+++ b/dom/system/gonk/RadioInterfaceLayer.js
@@ -1557,16 +1557,17 @@ RadioInterface.prototype = {
       }
     }
     if (dataDisconnecting) {
       if (this._radioOffTimer == null) {
         this._radioOffTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       }
       this._radioOffTimer.initWithCallback(this._fireRadioOffTimer.bind(this),
                                            RADIO_POWER_OFF_TIMEOUT, Ci.nsITimer.TYPE_ONE_SHOT);
+      this._radioOffAfterDataDisconnected = true;
       return;
     }
     this.setRadioEnabled(false);
   },
 
   /**
    * This function will do the following steps:
    *   1. Clear the cached APN settings in the RIL.
@@ -1999,31 +2000,32 @@ RadioInterface.prototype = {
     }
 
     this._deliverDataCallCallback("dataCallStateChanged",
                                   [datacall]);
 
     // Process pending radio power off request after all data calls
     // are disconnected.
     if (datacall.state == RIL.GECKO_NETWORK_STATE_UNKNOWN &&
-        this._changingRadioPower) {
+        this._radioOffAfterDataDisconnected) {
       let anyDataConnected = false;
       for each (let apnSetting in this.apnSettings.byApn) {
         for each (let type in apnSetting.types) {
           if (this.getDataCallStateByType(type) == RIL.GECKO_NETWORK_STATE_CONNECTED) {
             anyDataConnected = true;
             break;
           }
         }
         if (anyDataConnected) {
           break;
         }
       }
       if (!anyDataConnected) {
         if (DEBUG) this.debug("All data connections are disconnected, set radio off.");
+        this._radioOffAfterDataDisconnected = false;
         this._cancelRadioOffTimer();
         this.setRadioEnabled(false);
       }
     }
   },
 
   /**
    * Handle data call list.
@@ -2277,16 +2279,20 @@ RadioInterface.prototype = {
   // Flag to determine the radio state to start with when we boot up. It
   // corresponds to the 'ril.radio.disabled' setting from the UI.
   _radioEnabled: null,
 
   // Flag to ignore any radio power change requests during We're changing
   // the radio power.
   _changingRadioPower: false,
 
+  // Flag to determine if we need to set radio off when we are notified a data
+  // call has been disconnected.
+  _radioOffAfterDataDisconnected: false,
+
   // Data calls setting.
   dataCallSettings: null,
 
   apnSettings: null,
 
   // Flag to determine whether to update system clock automatically. It
   // corresponds to the 'time.clock.automatic-update.enabled' setting.
   _clockAutoUpdateEnabled: null,
--- a/dom/system/gonk/ril_consts.js
+++ b/dom/system/gonk/ril_consts.js
@@ -2374,20 +2374,19 @@ this.CALL_FAIL_IMSI_UNKNOWN_IN_VLR = 242
 this.CALL_FAIL_IMEI_NOT_ACCEPTED = 243;
 this.CALL_FAIL_ERROR_UNSPECIFIED = 0xffff;
 
 // Other Gecko-specific constants
 this.GECKO_RADIOSTATE_UNAVAILABLE   = null;
 this.GECKO_RADIOSTATE_OFF           = "off";
 this.GECKO_RADIOSTATE_READY         = "ready";
 
-this.GECKO_CARDSTATE_NOT_READY                     = null;
+this.GECKO_CARDSTATE_UNDETECTED                    = null;
 this.GECKO_CARDSTATE_ILLEGAL                       = "illegal";
 this.GECKO_CARDSTATE_UNKNOWN                       = "unknown";
-this.GECKO_CARDSTATE_ABSENT                        = "absent";
 this.GECKO_CARDSTATE_PIN_REQUIRED                  = "pinRequired";
 this.GECKO_CARDSTATE_PUK_REQUIRED                  = "pukRequired";
 this.GECKO_CARDSTATE_PERSONALIZATION_IN_PROGRESS   = "personalizationInProgress";
 this.GECKO_CARDSTATE_PERSONALIZATION_READY         = "personalizationReady";
 this.GECKO_CARDSTATE_NETWORK_LOCKED                = "networkLocked";
 this.GECKO_CARDSTATE_NETWORK_SUBSET_LOCKED         = "networkSubsetLocked";
 this.GECKO_CARDSTATE_CORPORATE_LOCKED              = "corporateLocked";
 this.GECKO_CARDSTATE_SERVICE_PROVIDER_LOCKED       = "serviceProviderLocked";
--- a/dom/system/gonk/ril_worker.js
+++ b/dom/system/gonk/ril_worker.js
@@ -273,17 +273,17 @@ let RIL = {
      * ICC status. Keeps a reference of the data response to the
      * getICCStatus request.
      */
     this.iccStatus = null;
 
     /**
      * Card state
      */
-    this.cardState = GECKO_CARDSTATE_UNKNOWN;
+    this.cardState = GECKO_CARDSTATE_UNDETECTED;
 
     /**
      * Strings
      */
     this.IMEI = null;
     this.IMEISV = null;
     this.ESN = null;
     this.MEID = null;
@@ -2949,61 +2949,46 @@ let RIL = {
   },
 
   /**
    * Process ICC status.
    */
   _processICCStatus: function _processICCStatus(iccStatus) {
     this.iccStatus = iccStatus;
     let newCardState;
-
-    if ((!iccStatus) || (iccStatus.cardState == CARD_STATE_ABSENT)) {
-      switch (this.radioState) {
-        case GECKO_RADIOSTATE_UNAVAILABLE:
-          newCardState = GECKO_CARDSTATE_UNKNOWN;
-          break;
-        case GECKO_RADIOSTATE_OFF:
-          newCardState = GECKO_CARDSTATE_NOT_READY;
-          break;
-        case GECKO_RADIOSTATE_READY:
-          if (DEBUG) {
-            debug("ICC absent");
-          }
-          newCardState = GECKO_CARDSTATE_ABSENT;
-          break;
-      }
-      if (newCardState == this.cardState) {
-        return;
-      }
-      this.iccInfo = {iccType: null};
-      ICCUtilsHelper.handleICCInfoChange();
-
-      this.cardState = newCardState;
-      this.sendChromeMessage({rilMessageType: "cardstatechange",
-                              cardState: this.cardState});
-      return;
-    }
-
     let index = this._isCdma ? iccStatus.cdmaSubscriptionAppIndex :
                                iccStatus.gsmUmtsSubscriptionAppIndex;
     let app = iccStatus.apps[index];
-    if (iccStatus.cardState == CARD_STATE_ERROR || !app) {
-      if (this.cardState == GECKO_CARDSTATE_UNKNOWN) {
+
+    // When |iccStatus.cardState| is not CARD_STATE_PRESENT or have incorrect
+    // app information, we can not get iccId. So treat ICC as undetected.
+    if (iccStatus.cardState !== CARD_STATE_PRESENT || !app) {
+      if (this.cardState !== GECKO_CARDSTATE_UNDETECTED) {
         this.operator = null;
-        return;
-      }
-      this.operator = null;
-      this.cardState = GECKO_CARDSTATE_UNKNOWN;
-      this.sendChromeMessage({rilMessageType: "cardstatechange",
-                              cardState: this.cardState});
+        // We should send |cardstatechange| before |iccinfochange|, otherwise we
+        // may lost cardstatechange event when icc card becomes undetected.
+        this.cardState = GECKO_CARDSTATE_UNDETECTED;
+        this.sendChromeMessage({rilMessageType: "cardstatechange",
+                                cardState: this.cardState});
+
+        this.iccInfo = {iccType: null};
+        ICCUtilsHelper.handleICCInfoChange();
+      }
       return;
     }
+
     // fetchICCRecords will need to read aid, so read aid here.
     this.aid = app.aid;
     this.appType = app.app_type;
+    this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
+    // Try to get iccId only when cardState left GECKO_CARDSTATE_UNDETECTED.
+    if (this.cardState === GECKO_CARDSTATE_UNDETECTED &&
+        iccStatus.cardState === CARD_STATE_PRESENT) {
+      ICCRecordHelper.readICCID();
+    }
 
     switch (app.app_state) {
       case CARD_APPSTATE_ILLEGAL:
         newCardState = GECKO_CARDSTATE_ILLEGAL;
         break;
       case CARD_APPSTATE_PIN:
         newCardState = GECKO_CARDSTATE_PIN_REQUIRED;
         break;
@@ -3031,18 +3016,16 @@ let RIL = {
 
     if (this.cardState == newCardState) {
       return;
     }
 
     // This was moved down from CARD_APPSTATE_READY
     this.requestNetworkInfo();
     if (newCardState == GECKO_CARDSTATE_READY) {
-      this.iccInfo.iccType = GECKO_CARD_TYPE[this.appType];
-
       // For type SIM, we need to check EF_phase first.
       // Other types of ICC we can send Terminal_Profile immediately.
       if (this.appType == CARD_APPTYPE_SIM) {
         ICCRecordHelper.readICCPhase();
         ICCRecordHelper.fetchICCRecords();
       } else if (this.appType == CARD_APPTYPE_USIM) {
         if (RILQUIRKS_SEND_STK_PROFILE_DOWNLOAD) {
           this.sendStkTerminalProfile(STK_SUPPORTED_TERMINAL_PROFILE);
@@ -11200,17 +11183,16 @@ ICCIOHelper[ICC_COMMAND_UPDATE_RECORD] =
 /**
  * Helper for ICC records.
  */
 let ICCRecordHelper = {
   /**
    * Fetch ICC records.
    */
   fetchICCRecords: function fetchICCRecords() {
-    this.readICCID();
     RIL.getIMSI();
     this.readAD();
     this.readSST();
   },
 
   /**
    * Read EF_phase.
    * This EF is only available in SIM.
@@ -13176,17 +13158,16 @@ let ICCContactHelper = {
       ICCRecordHelper.updateIAP(pbr.iap.fileId, recordNumber, iap, onsuccess, onerror);
     }.bind(this);
     ICCRecordHelper.readIAP(pbr.iap.fileId, recordNumber, gotIAPCb, onerror);
   },
 };
 
 let RuimRecordHelper = {
   fetchRuimRecords: function fetchRuimRecords() {
-    ICCRecordHelper.readICCID();
     this.getIMSI_M();
     this.readCST();
     this.readCDMAHome();
     RIL.getCdmaSubscription();
   },
 
   /**
    * Get IMSI_M from CSIM/RUIM.
--- a/dom/system/gonk/tests/test_ril_worker_icc.js
+++ b/dom/system/gonk/tests/test_ril_worker_icc.js
@@ -1714,18 +1714,21 @@ add_test(function test_find_free_icc_con
 
   run_next_test();
 });
 
 add_test(function test_personalization_state() {
   let worker = newUint8Worker();
   let ril = worker.RIL;
 
+  worker.ICCRecordHelper.readICCID = function fakeReadICCID() {};
+
   function testPersonalization(cardPersoState, geckoCardState) {
     let iccStatus = {
+      cardState: CARD_STATE_PRESENT,
       gsmUmtsSubscriptionAppIndex: 0,
       apps: [
         {
           app_state: CARD_APPSTATE_SUBSCRIPTION_PERSO,
           perso_substate: cardPersoState
         }],
     };
 
@@ -1753,18 +1756,21 @@ add_test(function test_personalization_s
 
 /**
  * Verify SIM app_state in _processICCStatus
  */
 add_test(function test_card_app_state() {
   let worker = newUint8Worker();
   let ril = worker.RIL;
 
+  worker.ICCRecordHelper.readICCID = function fakeReadICCID() {};
+
   function testCardAppState(cardAppState, geckoCardState) {
     let iccStatus = {
+      cardState: CARD_STATE_PRESENT,
       gsmUmtsSubscriptionAppIndex: 0,
       apps: [
       {
         app_state: cardAppState
       }],
     };
 
     ril._processICCStatus(iccStatus);
@@ -1789,18 +1795,21 @@ add_test(function test_card_app_state() 
 
 /**
  * Verify permanent blocked for ICC.
  */
 add_test(function test_icc_permanent_blocked() {
   let worker = newUint8Worker();
   let ril = worker.RIL;
 
+  worker.ICCRecordHelper.readICCID = function fakeReadICCID() {};
+
   function testPermanentBlocked(pin1_replaced, universalPINState, pin1) {
     let iccStatus = {
+      cardState: CARD_STATE_PRESENT,
       gsmUmtsSubscriptionAppIndex: 0,
       universalPINState: universalPINState,
       apps: [
       {
         pin1_replaced: pin1_replaced,
         pin1: pin1
       }]
     };
--- a/dom/system/gonk/tests/test_ril_worker_ruim.js
+++ b/dom/system/gonk/tests/test_ril_worker_ruim.js
@@ -73,20 +73,16 @@ add_test(function test_fetch_ruim_recode
   let worker = newWorker();
   let RIL = worker.RIL;
   let iccHelper = worker.ICCRecordHelper;
   let ruimHelper = worker.RuimRecordHelper;
 
   function testFetchRuimRecordes(expectCalled) {
     let ifCalled = [];
 
-    iccHelper.readICCID = function () {
-      ifCalled.push("readICCID");
-    };
-
     ruimHelper.getIMSI_M = function () {
       ifCalled.push("getIMSI_M");
     };
 
     ruimHelper.readCST = function () {
       ifCalled.push("readCST");
     };
 
@@ -103,17 +99,17 @@ add_test(function test_fetch_ruim_recode
     for (let i = 0; i < expectCalled.length; i++ ) {
       if (ifCalled[i] != expectCalled[i]) {
         do_print(expectCalled[i] + " is not called.");
         do_check_true(false);
       }
     }
   }
 
-  let expectCalled = ["readICCID", "getIMSI_M", "readCST", "readCDMAHome",
+  let expectCalled = ["getIMSI_M", "readCST", "readCDMAHome",
                       "getCdmaSubscription"];
   testFetchRuimRecordes(expectCalled);
 
   run_next_test();
 });
 
 /**
  * Verify RuimRecordHelper.decodeIMSIValue
--- a/dom/telephony/test/marionette/test_outgoing_radio_off.js
+++ b/dom/telephony/test/marionette/test_outgoing_radio_off.js
@@ -21,24 +21,23 @@ function changeSetting(key, value, callb
   setReq.addEventListener("error", function onSetError() {
     ok(false, "cannot set '" + key + "'");
     cleanUp();
   });
 }
 
 function setRadioEnabled(enabled, callback) {
   changeSetting("ril.radio.disabled", !enabled, function() {
-    icc.addEventListener("cardstatechange", function handler() {
-      // Wait until card state changes to "ready" after turning on radio.
-      // Wait until card state changes to "not-ready" after turning off radio.
-      if ((enabled && icc.cardState == "ready") ||
-          (!enabled && icc.cardState != "ready")) {
-        icc.removeEventListener("cardstatechange", handler);
-        callback();
-      }
+    // Wait for iccdetected event after turning on radio.
+    // Wait for iccundetected event after turning off radio.
+    let event = (enabled) ? "iccdetected" : "iccundetected";
+    icc.addEventListener(event, function handler(evt) {
+      log(event + ": " + evt.iccId);
+      icc.removeEventListener(event, handler);
+      callback();
     });
   });
 }
 
 function dial(number) {
   // Verify initial state before dial.
   ok(telephony);
   is(telephony.active, null);
--- a/dom/tests/mochitest/general/test_interfaces.html
+++ b/dom/tests/mochitest/general/test_interfaces.html
@@ -291,16 +291,17 @@ var interfaceNamesInGlobalScope =
     "HTMLTableSectionElement",
     "HTMLTemplateElement",
     "HTMLTextAreaElement",
     "HTMLTimeElement",
     "HTMLTitleElement",
     "HTMLUListElement",
     "HTMLUnknownElement",
     "HTMLVideoElement",
+    {name: "IccChangeEvent", b2g: true, pref: "dom.icc.enabled"},
     {name: "IccCardLockError", b2g: true, pref: "dom.icc.enabled"},
     "IDBCursor",
     "IDBCursorWithValue",
     "IDBDatabase",
     "IDBFactory",
     "IDBFileHandle",
     "IDBIndex",
     "IDBKeyRange",
@@ -340,16 +341,17 @@ var interfaceNamesInGlobalScope =
     {name: "MozCellBroadcast", b2g: true, pref: "dom.cellbroadcast.enabled"},
     {name: "MozCellBroadcastEvent", b2g: true, pref: "dom.cellbroadcast.enabled"},
     "MozConnection",
     "mozContact",
     "MozContactChangeEvent",
     "MozCSSKeyframeRule",
     "MozCSSKeyframesRule",
     {name: "MozEmergencyCbModeEvent", b2g: true, pref: "dom.mobileconnection.enabled"},
+    {name: "MozIcc", b2g: true, pref: "dom.icc.enabled"},
     {name: "MozIccManager", b2g: true, pref: "dom.icc.enabled"},
     {name: "MozInputContext", b2g: true},
     {name: "MozInputMethodManager", b2g: true},
     "MozMmsEvent",
     "MozMmsMessage",
     {name: "MozMobileConnection", b2g: true, pref: "dom.mobileconnection.enabled"},
     {name: "MozMobileConnectionArray", b2g: true, pref: "dom.mobileconnection.enabled"},
     "MozMobileMessageManager",
new file mode 100644
--- /dev/null
+++ b/dom/webidl/IccChangeEvent.webidl
@@ -0,0 +1,17 @@
+/* -*- Mode: IDL; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/.
+ */
+
+[Pref="dom.icc.enabled",
+ Constructor(DOMString type, optional IccChangeEventInit eventInitDict)]
+interface IccChangeEvent : Event
+{
+  readonly attribute DOMString iccId;
+};
+
+dictionary IccChangeEventInit : EventInit
+{
+  DOMString iccId = "";
+};
new file mode 100644
--- /dev/null
+++ b/dom/webidl/MozIcc.webidl
@@ -0,0 +1,368 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+interface MozIccInfo;
+
+[Pref="dom.icc.enabled"]
+interface MozIcc : EventTarget
+{
+  // Integrated Circuit Card Information.
+
+  /**
+   * Information stored in the device's ICC.
+   *
+   * Once the ICC becomes undetectable, iccinfochange event will be notified.
+   * Also, the attribute is set to null and this MozIcc object becomes invalid.
+   * Calling asynchronous functions raises exception then.
+   */
+  readonly attribute MozIccInfo? iccInfo;
+
+  /**
+   * The 'iccinfochange' event is notified whenever the icc info object
+   * changes.
+   */
+  attribute EventHandler oniccinfochange;
+
+  // Integrated Circuit Card State.
+
+  /**
+   * Indicates the state of the device's ICC.
+   *
+   * Possible values: 'illegal', 'unknown', 'pinRequired',
+   * 'pukRequired', 'personalizationInProgress', 'networkLocked',
+   * 'corporateLocked', 'serviceProviderLocked', 'networkPukRequired',
+   * 'corporatePukRequired', 'serviceProviderPukRequired',
+   * 'personalizationReady', 'ready', 'permanentBlocked'.
+   *
+   * Once the ICC becomes undetectable, cardstatechange event will be notified.
+   * Also, the attribute is set to null and this MozIcc object becomes invalid.
+   * Calling asynchronous functions raises exception then.
+   */
+  readonly attribute DOMString? cardState;
+
+  /**
+   * The 'cardstatechange' event is notified when the 'cardState' attribute
+   * changes value.
+   */
+  attribute EventHandler oncardstatechange;
+
+  // Integrated Circuit Card STK.
+
+  /**
+   * Send the response back to ICC after an attempt to execute STK proactive
+   * Command.
+   *
+   * @param command
+   *        Command received from ICC. See MozStkCommand.
+   * @param response
+   *        The response that will be sent to ICC.
+   *        @see MozStkResponse for the detail of response.
+   */
+  [Throws]
+  void sendStkResponse(any command, any response);
+
+  /**
+   * Send the "Menu Selection" envelope command to ICC for menu selection.
+   *
+   * @param itemIdentifier
+   *        The identifier of the item selected by user.
+   * @param helpRequested
+   *        true if user requests to provide help information, false otherwise.
+   */
+  [Throws]
+  void sendStkMenuSelection(unsigned short itemIdentifier,
+                            boolean helpRequested);
+
+  /**
+   * Send the "Timer Expiration" envelope command to ICC for TIMER MANAGEMENT.
+   *
+   * @param timer
+   *        The identifier and value for a timer.
+   *        timerId: Identifier of the timer that has expired.
+   *        timerValue: Different between the time when this command is issued
+   *                    and when the timer was initially started.
+   *        @see MozStkTimer
+   */
+  [Throws]
+  void sendStkTimerExpiration(any timer);
+
+  /**
+   * Send "Event Download" envelope command to ICC.
+   * ICC will not respond with any data for this command.
+   *
+   * @param event
+   *        one of events below:
+   *        - MozStkLocationEvent
+   *        - MozStkCallEvent
+   *        - MozStkLanguageSelectionEvent
+   *        - MozStkGeneralEvent
+   *        - MozStkBrowserTerminationEvent
+   */
+  [Throws]
+  void sendStkEventDownload(any event);
+
+  /**
+   * The 'stkcommand' event is notified whenever STK proactive command is
+   * issued from ICC.
+   */
+  attribute EventHandler onstkcommand;
+
+  /**
+   * 'stksessionend' event is notified whenever STK session is terminated by
+   * ICC.
+   */
+  attribute EventHandler onstksessionend;
+
+  // Integrated Circuit Card Lock interfaces.
+
+  /**
+   * Find out about the status of an ICC lock (e.g. the PIN lock).
+   *
+   * @param lockType
+   *        Identifies the lock type, e.g. "pin" for the PIN lock, "fdn" for
+   *        the FDN lock.
+   *
+   * @return a DOMRequest.
+   *         The request's result will be an object containing
+   *         information about the specified lock's status,
+   *         e.g. {lockType: "pin", enabled: true}.
+   */
+  [Throws]
+  nsISupports getCardLock(DOMString lockType);
+
+  /**
+   * Unlock a card lock.
+   *
+   * @param info
+   *        An object containing the information necessary to unlock
+   *        the given lock. At a minimum, this object must have a
+   *        "lockType" attribute which specifies the type of lock, e.g.
+   *        "pin" for the PIN lock. Other attributes are dependent on
+   *        the lock type.
+   *
+   * Examples:
+   *
+   * (1) Unlocking the PIN:
+   *
+   *   unlockCardLock({lockType: "pin",
+   *                   pin: "..."});
+   *
+   * (2) Unlocking the PUK and supplying a new PIN:
+   *
+   *   unlockCardLock({lockType: "puk",
+   *                   puk: "...",
+   *                   newPin: "..."});
+   *
+   * (3) Network depersonalization. Unlocking the network control key (NCK).
+   *
+   *   unlockCardLock({lockType: "nck",
+   *                   pin: "..."});
+   *
+   * (4) Corporate depersonalization. Unlocking the corporate control key (CCK).
+   *
+   *   unlockCardLock({lockType: "cck",
+   *                   pin: "..."});
+   *
+   * (5) Service Provider depersonalization. Unlocking the service provider
+   *     control key (SPCK).
+   *
+   *   unlockCardLock({lockType: "spck",
+   *                   pin: "..."});
+   *
+   * (6) Network PUK depersonalization. Unlocking the network control key (NCK).
+   *
+   *   unlockCardLock({lockType: "nckPuk",
+   *                   puk: "..."});
+   *
+   * (7) Corporate PUK depersonalization. Unlocking the corporate control key
+   *     (CCK).
+   *
+   *   unlockCardLock({lockType: "cckPuk",
+   *                   puk: "..."});
+   *
+   * (8) Service Provider PUK depersonalization. Unlocking the service provider
+   *     control key (SPCK).
+   *
+   *   unlockCardLock({lockType: "spckPuk",
+   *                   puk: "..."});
+   *
+   * @return a DOMRequest.
+   *         The request's result will be an object containing
+   *         information about the unlock operation.
+   *
+   * Examples:
+   *
+   * (1) Unlocking failed:
+   *
+   *     {
+   *       lockType:   "pin",
+   *       success:    false,
+   *       retryCount: 2
+   *     }
+   *
+   * (2) Unlocking succeeded:
+   *
+   *     {
+   *       lockType:  "pin",
+   *       success:   true
+   *     }
+   */
+  [Throws]
+  nsISupports unlockCardLock(any info);
+
+  /**
+   * Modify the state of a card lock.
+   *
+   * @param info
+   *        An object containing information about the lock and
+   *        how to modify its state. At a minimum, this object
+   *        must have a "lockType" attribute which specifies the
+   *        type of lock, e.g. "pin" for the PIN lock. Other
+   *        attributes are dependent on the lock type.
+   *
+   * Examples:
+   *
+   * (1a) Disabling the PIN lock:
+   *
+   *   setCardLock({lockType: "pin",
+   *                pin: "...",
+   *                enabled: false});
+   *
+   * (1b) Disabling the FDN lock:
+   *
+   *   setCardLock({lockType: "fdn",
+   *                pin2: "...",
+   *                enabled: false});
+   *
+   * (2) Changing the PIN:
+   *
+   *   setCardLock({lockType: "pin",
+   *                pin: "...",
+   *                newPin: "..."});
+   *
+   * @return a DOMRequest.
+   *         The request's result will be an object containing
+   *         information about the operation.
+   *
+   * Examples:
+   *
+   * (1) Enabling/Disabling card lock failed or change card lock failed.
+   *
+   *     {
+   *       lockType: "pin",
+   *       success: false,
+   *       retryCount: 2
+   *     }
+   *
+   * (2) Enabling/Disabling card lock succeed or change card lock succeed.
+   *
+   *     {
+   *       lockType: "pin",
+   *       success: true
+   *     }
+   */
+  [Throws]
+  nsISupports setCardLock(any info);
+
+  /**
+   * Retrieve the number of remaining tries for unlocking the card.
+   *
+   * @param lockType
+   *        Identifies the lock type, e.g. "pin" for the PIN lock, "puk" for
+   *        the PUK lock.
+   *
+   * @return a DOMRequest.
+   *         If the lock type is "pin", or "puk", the request's result will be
+   *         an object containing the number of retries for the specified
+   *         lock. For any other lock type, the result is undefined.
+   */
+  [Throws]
+  nsISupports getCardLockRetryCount(DOMString lockType);
+
+  // Integrated Circuit Card Phonebook Interfaces.
+
+  /**
+   * Read ICC contacts.
+   *
+   * @param contactType
+   *        One of type as below,
+   *        - 'adn': Abbreviated Dialling Number.
+   *        - 'fdn': Fixed Dialling Number.
+   *
+   * @return a DOMRequest.
+   */
+  [Throws]
+  nsISupports readContacts(DOMString contactType);
+
+  /**
+   * Update ICC Phonebook contact.
+   *
+   * @param contactType
+   *        One of type as below,
+   *        - 'adn': Abbreviated Dialling Number.
+   *        - 'fdn': Fixed Dialling Number.
+   * @param contact
+   *        The contact will be updated in ICC.
+   * @param [optional] pin2
+   *        PIN2 is only required for 'fdn'.
+   *
+   * @return a DOMRequest.
+   */
+  [Throws]
+  nsISupports updateContact(DOMString contactType,
+                           any contact,
+                           optional DOMString? pin2 = null);
+
+  // Integrated Circuit Card Secure Element Interfaces.
+
+  /**
+   * A secure element is a smart card chip that can hold
+   * several different applications with the necessary security.
+   * The most known secure element is the Universal Integrated Circuit Card
+   * (UICC).
+   */
+
+  /**
+   * Send request to open a logical channel defined by its
+   * application identifier (AID).
+   *
+   * @param aid
+   *        The application identifier of the applet to be selected on this
+   *        channel.
+   *
+   * @return a DOMRequest.
+   *         The request's result will be an instance of channel (channelID)
+   *         if available or null.
+   */
+  [Throws]
+  nsISupports iccOpenChannel(DOMString aid);
+
+  /**
+   * Interface, used to communicate with an applet through the
+   * application data protocol units (APDUs) and is
+   * used for all data that is exchanged between the UICC and the terminal (ME).
+   *
+   * @param channel
+   *        The application identifier of the applet to which APDU is directed.
+   * @param apdu
+   *        Application protocol data unit.
+   *
+   * @return a DOMRequest.
+   *         The request's result will be response APDU.
+   */
+  [Throws]
+  nsISupports iccExchangeAPDU(long channel, any apdu);
+
+  /**
+   * Send request to close the selected logical channel identified by its
+   * application identifier (AID).
+   *
+   * @param aid
+   *        The application identifier of the applet, to be closed.
+   *
+   * @return a DOMRequest.
+   */
+  [Throws]
+  nsISupports iccCloseChannel(long channel);
+};
--- a/dom/webidl/moz.build
+++ b/dom/webidl/moz.build
@@ -508,16 +508,17 @@ if CONFIG['MOZ_B2G_BT']:
 if CONFIG['MOZ_B2G_RIL']:
     WEBIDL_FILES += [
         'CFStateChangeEvent.webidl',
         'DataErrorEvent.webidl',
         'IccCardLockError.webidl',
         'MozCellBroadcast.webidl',
         'MozCellBroadcastEvent.webidl',
         'MozEmergencyCbModeEvent.webidl',
+        'MozIcc.webidl',
         'MozMobileConnectionArray.webidl',
         'MozOtaStatusEvent.webidl',
         'MozVoicemail.webidl',
         'MozVoicemailEvent.webidl',
         'USSDReceivedEvent.webidl',
     ]
 
 if CONFIG['MOZ_NFC']:
@@ -559,16 +560,17 @@ if CONFIG['ENABLE_TESTS']:
 
 GENERATED_EVENTS_WEBIDL_FILES = [
     'BlobEvent.webidl',
     'CallGroupErrorEvent.webidl',
     'DataStoreChangeEvent.webidl',
     'DeviceLightEvent.webidl',
     'DeviceProximityEvent.webidl',
     'ErrorEvent.webidl',
+    'IccChangeEvent.webidl',
     'MediaStreamEvent.webidl',
     'MozContactChangeEvent.webidl',
     'MozInterAppMessageEvent.webidl',
     'MozStkCommandEvent.webidl',
     'RTCDataChannelEvent.webidl',
     'RTCPeerConnectionIceEvent.webidl',
     'TrackEvent.webidl',
     'UserProximityEvent.webidl',
--- a/dom/wifi/DOMWifiManager.js
+++ b/dom/wifi/DOMWifiManager.js
@@ -52,17 +52,18 @@ DOMWifiManager.prototype = {
   classInfo: XPCOMUtils.generateCI({classID: DOMWIFIMANAGER_CID,
                                     contractID: DOMWIFIMANAGER_CONTRACTID,
                                     classDescription: "DOMWifiManager",
                                     interfaces: [Ci.nsIDOMWifiManager],
                                     flags: Ci.nsIClassInfo.DOM_OBJECT}),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIDOMWifiManager,
                                          Ci.nsIDOMGlobalPropertyInitializer,
-                                         Ci.nsISupportsWeakReference]),
+                                         Ci.nsISupportsWeakReference,
+                                         Ci.nsIObserver]),
 
   // nsIDOMGlobalPropertyInitializer implementation
   init: function(aWindow) {
     let principal = aWindow.document.nodePrincipal;
     let secMan = Cc["@mozilla.org/scriptsecuritymanager;1"].getService(Ci.nsIScriptSecurityManager);
 
     let perm = principal == secMan.getSystemPrincipal()
                  ? Ci.nsIPermissionManager.ALLOW_ACTION
--- a/layout/ipc/RenderFrameParent.h
+++ b/layout/ipc/RenderFrameParent.h
@@ -216,16 +216,22 @@ public:
   BuildLayer(nsDisplayListBuilder* aBuilder, LayerManager* aManager,
              const ContainerLayerParameters& aContainerParameters) MOZ_OVERRIDE;
 
   void HitTest(nsDisplayListBuilder* aBuilder, const nsRect& aRect,
                HitTestState* aState, nsTArray<nsIFrame*> *aOutFrames) MOZ_OVERRIDE;
 
   NS_DISPLAY_DECL_NAME("Remote", TYPE_REMOTE)
 
+  virtual nsRegion GetOpaqueRegion(nsDisplayListBuilder* aBuilder,
+                                   bool* aSnap)
+  {
+    return GetBounds(aBuilder, aSnap);
+  }
+
 private:
   RenderFrameParent* mRemoteFrame;
 };
 
 /**
  * nsDisplayRemoteShadow is a way of adding display items for frames in a
  * separate process, for hit testing only. After being processed, the hit
  * test state will contain IDs for any remote frames that were hit.
--- a/netwerk/protocol/rtsp/controller/RtspController.cpp
+++ b/netwerk/protocol/rtsp/controller/RtspController.cpp
@@ -79,85 +79,85 @@ RtspController::Play(void)
 {
   LOG(("RtspController::Play()"));
   if (!mRtspSource.get()) {
     MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!");
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (mState != CONNECTED) {
-    return NS_ERROR_UNEXPECTED;
+    return NS_ERROR_NOT_CONNECTED;
   }
 
   mRtspSource->play();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RtspController::Pause(void)
 {
   LOG(("RtspController::Pause()"));
   if (!mRtspSource.get()) {
     MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!");
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (mState != CONNECTED) {
-    return NS_ERROR_UNEXPECTED;
+    return NS_ERROR_NOT_CONNECTED;
   }
 
   mRtspSource->pause();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RtspController::Resume(void)
 {
   LOG(("RtspController::Resume()"));
   if (!mRtspSource.get()) {
     MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!");
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (mState != CONNECTED) {
-    return NS_ERROR_UNEXPECTED;
+    return NS_ERROR_NOT_CONNECTED;
   }
 
   mRtspSource->play();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RtspController::Suspend(void)
 {
   LOG(("RtspController::Suspend()"));
   if (!mRtspSource.get()) {
     MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!");
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (mState != CONNECTED) {
-    return NS_ERROR_UNEXPECTED;
+    return NS_ERROR_NOT_CONNECTED;
   }
 
   mRtspSource->pause();
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RtspController::Seek(uint64_t seekTimeUs)
 {
   LOG(("RtspController::Seek() %llu", seekTimeUs));
   if (!mRtspSource.get()) {
     MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!");
     return NS_ERROR_NOT_INITIALIZED;
   }
 
   if (mState != CONNECTED) {
-    return NS_ERROR_UNEXPECTED;
+    return NS_ERROR_NOT_CONNECTED;
   }
 
   mRtspSource->seek(seekTimeUs);
   return NS_OK;
 }
 
 NS_IMETHODIMP
 RtspController::Stop()
--- a/netwerk/protocol/rtsp/controller/RtspControllerChild.cpp
+++ b/netwerk/protocol/rtsp/controller/RtspControllerChild.cpp
@@ -105,33 +105,30 @@ RtspControllerChild::GetMetaDataLength()
   return mMetaArray.Length();
 }
 
 bool
 RtspControllerChild::RecvOnConnected(
                        const uint8_t& index,
                        const InfallibleTArray<RtspMetadataParam>& metaArray)
 {
-  uint32_t tracks;
-
   // Deserialize meta data.
   nsRefPtr<RtspMetaData> meta = new RtspMetaData();
   nsresult rv = meta->DeserializeRtspMetaData(metaArray);
   NS_ENSURE_SUCCESS(rv, true);
-  meta->GetTotalTracks(&tracks);
-  if (tracks <= 0) {
-    LOG(("RtspControllerChild::RecvOnConnected invalid tracks %d", tracks));
+  meta->GetTotalTracks(&mTotalTracks);
+  if (mTotalTracks <= 0) {
+    LOG(("RtspControllerChild::RecvOnConnected invalid tracks %d", mTotalTracks));
     // Set the default value.
-    tracks = kRtspTotalTracks;
+    mTotalTracks = kRtspTotalTracks;
   }
-  mTotalTracks = tracks;
   AddMetaData(meta.forget());
 
   // Notify the listener when meta data of tracks are available.
-  if ((index + 1) == tracks) {
+  if ((index + 1) == mTotalTracks) {
     // The controller provide |GetTrackMetaData| method for his client.
     if (mListener) {
       mListener->OnConnected(index, nullptr);
     }
   }
   return true;
 }
 
--- a/netwerk/protocol/rtsp/controller/RtspControllerParent.cpp
+++ b/netwerk/protocol/rtsp/controller/RtspControllerParent.cpp
@@ -14,27 +14,35 @@
 
 #include <sys/types.h>
 #include <sys/socket.h>
 
 PRLogModuleInfo* gRtspLog;
 #undef LOG
 #define LOG(args) PR_LOG(gRtspLog, PR_LOG_DEBUG, args)
 
+#define SEND_DISCONNECT_IF_ERROR(rv)                         \
+  if (NS_FAILED(rv) && mIPCOpen && mTotalTracks > 0) {       \
+    for (int i = 0; i < mTotalTracks; i++) {                 \
+      SendOnDisconnected(i, rv);                             \
+    }                                                        \
+  }
+
 using namespace mozilla::ipc;
 
 namespace mozilla {
 namespace net {
 
 NS_IMPL_ISUPPORTS2(RtspControllerParent,
                    nsIInterfaceRequestor,
                    nsIStreamingProtocolListener)
 
 RtspControllerParent::RtspControllerParent()
   : mIPCOpen(true)
+  , mTotalTracks(0)
 {
 #if defined(PR_LOGGING)
   if (!gRtspLog)
     gRtspLog = PR_NewLogModule("nsRtsp");
 #endif
 }
 
 RtspControllerParent::~RtspControllerParent()
@@ -70,61 +78,61 @@ RtspControllerParent::RecvAsyncOpen(cons
 
 bool
 RtspControllerParent::RecvPlay()
 {
   LOG(("RtspControllerParent::RecvPlay()"));
   NS_ENSURE_TRUE(mController, true);
 
   nsresult rv = mController->Play();
-  NS_ENSURE_SUCCESS(rv, true);
+  SEND_DISCONNECT_IF_ERROR(rv)
   return true;
 }
 
 bool
 RtspControllerParent::RecvPause()
 {
   LOG(("RtspControllerParent::RecvPause()"));
   NS_ENSURE_TRUE(mController, true);
 
   nsresult rv = mController->Pause();
-  NS_ENSURE_SUCCESS(rv, true);
+  SEND_DISCONNECT_IF_ERROR(rv)
   return true;
 }
 
 bool
 RtspControllerParent::RecvResume()
 {
   LOG(("RtspControllerParent::RecvResume()"));
   NS_ENSURE_TRUE(mController, true);
 
   nsresult rv = mController->Resume();
-  NS_ENSURE_SUCCESS(rv, true);
+  SEND_DISCONNECT_IF_ERROR(rv)
   return true;
 }
 
 bool
 RtspControllerParent::RecvSuspend()
 {
   LOG(("RtspControllerParent::RecvSuspend()"));
   NS_ENSURE_TRUE(mController, true);
 
   nsresult rv = mController->Suspend();
-  NS_ENSURE_SUCCESS(rv, true);
+  SEND_DISCONNECT_IF_ERROR(rv)
   return true;
 }
 
 bool
 RtspControllerParent::RecvSeek(const uint64_t& offset)
 {
   LOG(("RtspControllerParent::RecvSeek()"));
   NS_ENSURE_TRUE(mController, true);
 
   nsresult rv = mController->Seek(offset);
-  NS_ENSURE_SUCCESS(rv, true);
+  SEND_DISCONNECT_IF_ERROR(rv)
   return true;
 }
 
 bool
 RtspControllerParent::RecvStop()
 {
   LOG(("RtspControllerParent::RecvStop()"));
   NS_ENSURE_TRUE(mController, true);
@@ -178,19 +186,19 @@ RtspControllerParent::OnConnected(uint8_
   uint32_t int32Value;
   uint64_t int64Value;
 
   LOG(("RtspControllerParent:: OnConnected"));
   // Serialize meta data.
   InfallibleTArray<RtspMetadataParam> metaData;
   nsCString name;
   name.AssignLiteral("TRACKS");
-  nsresult rv = meta->GetTotalTracks(&int32Value);
+  nsresult rv = meta->GetTotalTracks(&mTotalTracks);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
-  metaData.AppendElement(RtspMetadataParam(name, int32Value));
+  metaData.AppendElement(RtspMetadataParam(name, mTotalTracks));
 
   name.AssignLiteral("MIMETYPE");
   nsCString mimeType;
   rv = meta->GetMimeType(mimeType);
   NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
   metaData.AppendElement(RtspMetadataParam(name, mimeType));
 
   name.AssignLiteral("WIDTH");
--- a/netwerk/protocol/rtsp/controller/RtspControllerParent.h
+++ b/netwerk/protocol/rtsp/controller/RtspControllerParent.h
@@ -43,13 +43,14 @@ class RtspControllerParent : public PRts
 
  private:
   bool mIPCOpen;
   void ActorDestroy(ActorDestroyReason why);
   // RTSP URL refer to a stream or an aggregate of streams.
   nsCOMPtr<nsIURI> mURI;
   // The nsIStreamingProtocolController implementation.
   nsCOMPtr<nsIStreamingProtocolController> mController;
+  uint32_t mTotalTracks;
 };
 
 } // namespace net
 } // namespace mozilla
 #endif // RtspControllerParent_h
--- a/netwerk/protocol/rtsp/rtsp/RTSPSource.cpp
+++ b/netwerk/protocol/rtsp/rtsp/RTSPSource.cpp
@@ -554,21 +554,22 @@ void RTSPSource::onDisconnected(const sp
     mState = DISCONNECTED;
     mFinalResult = err;
 
     if (mDisconnectReplyID != 0) {
         finishDisconnectIfPossible();
     }
     if (mListener) {
       // err is always set to UNKNOWN_ERROR from
-      // Android right now, rename err to NS_ERROR_NOT_CONNECTED.
-      mListener->OnDisconnected(0, NS_ERROR_NOT_CONNECTED);
+      // Android right now, rename err to NS_ERROR_NET_TIMEOUT.
+      mListener->OnDisconnected(0, NS_ERROR_NET_TIMEOUT);
     }
     mAudioTrack = NULL;
     mVideoTrack = NULL;
+    mTracks.clear();
 }
 
 void RTSPSource::finishDisconnectIfPossible() {
     if (mState != DISCONNECTED) {
         mHandler->disconnect();
         return;
     }
 
--- a/testing/mochitest/runtestsb2g.py
+++ b/testing/mochitest/runtestsb2g.py
@@ -4,16 +4,17 @@
 
 import json
 import os
 import posixpath
 import shutil
 import sys
 import tempfile
 import threading
+import time
 import traceback
 
 here = os.path.abspath(os.path.dirname(__file__))
 sys.path.insert(0, here)
 
 from runtests import Mochitest
 from runtests import MochitestUtilsMixin
 from runtests import MochitestOptions
@@ -138,21 +139,26 @@ class B2GMochitest(MochitestUtilsMixin):
             status = self.runner.wait()
             if status is None:
                 # the runner has timed out
                 status = 124
         except KeyboardInterrupt:
             log.info("runtests.py | Received keyboard interrupt.\n");
             status = -1
         except:
+            # XXX Bug 937684
+            time.sleep(5)
             traceback.print_exc()
             log.error("Automation Error: Received unexpected exception while running application\n")
             self.runner.check_for_crashes()
             status = 1
 
+        # XXX Bug 937684
+        time.sleep(5)
+
         self.stopWebServer(options)
         self.stopWebSocketServer(options)
 
         log.info("runtestsb2g.py | Running tests: end.")
 
         if manifest is not None:
             self.cleanup(manifest, options)
         return status
--- a/toolkit/components/captivedetect/captivedetect.js
+++ b/toolkit/components/captivedetect/captivedetect.js
@@ -293,36 +293,34 @@ CaptivePortalDetector.prototype = {
 
   _startDetection: function _startDetection() {
     debug('startDetection {site=' + this._canonicalSiteURL + ',content='
           + this._canonicalSiteExpectedContent + ',time=' + this._maxWaitingTime + '}');
     let self = this;
 
     let urlFetcher = new URLFetcher(this._canonicalSiteURL, this._maxWaitingTime);
 
-    let requestDone = this.executeCallback.bind(this, true);
-    urlFetcher.ontimeout = requestDone;
-    urlFetcher.onerror = requestDone;
+    let mayRetry = this._mayRetry.bind(this);
+
+    urlFetcher.ontimeout = mayRetry;
+    urlFetcher.onerror = mayRetry;
     urlFetcher.onsuccess = function (content) {
       if (self.validateContent(content)) {
-        requestDone();
+        self.executeCallback(true);
       } else {
         // Content of the canonical website has been overwrite
         self._startLogin();
       }
     };
     urlFetcher.onredirectorerror = function (status) {
       if (status >= 300 && status <= 399) {
         // The canonical website has been redirected to an unknown location
         self._startLogin();
-      } else if (self._runningRequest.retryCount++ < self._maxRetryCount) {
-        debug('startDetection-retry: ' + self._runningRequest.retryCount);
-        self._startDetection();
       } else {
-        requestDone();
+        mayRetry();
       }
     };
 
     this._runningRequest['urlFetcher'] = urlFetcher;
   },
 
   _startLogin: function _startLogin() {
     let id = this._allocateRequestId();
@@ -331,16 +329,25 @@ CaptivePortalDetector.prototype = {
       id: id,
       url: this._canonicalSiteURL,
     };
     this._loginObserver.attach();
     this._runningRequest['eventId'] = id;
     this._sendEvent(kOpenCaptivePortalLoginEvent, details);
   },
 
+  _mayRetry: function _mayRetry() {
+    if (this._runningRequest.retryCount++ < this._maxRetryCount) {
+      debug('retry-Detection: ' + this._runningRequest.retryCount + '/' + this._maxRetryCount);
+      this._startDetection();
+    } else {
+      this.executeCallback(true);
+    }
+  },
+
   executeCallback: function executeCallback(success) {
     if (this._runningRequest) {
       debug('callback executed');
       if (this._runningRequest.hasOwnProperty('callback')) {
         this._runningRequest.callback.complete(success);
       }
 
       // Continue the following request
--- a/widget/xpwidgets/APZCCallbackHelper.cpp
+++ b/widget/xpwidgets/APZCCallbackHelper.cpp
@@ -133,17 +133,17 @@ APZCCallbackHelper::GetDOMWindowUtils(co
     return utils.forget();
 }
 
 bool
 APZCCallbackHelper::GetScrollIdentifiers(const nsIContent* aContent,
                                          uint32_t* aPresShellIdOut,
                                          FrameMetrics::ViewID* aViewIdOut)
 {
-    if (!nsLayoutUtils::FindIDFor(aContent, aViewIdOut)) {
+    if (!aContent || !nsLayoutUtils::FindIDFor(aContent, aViewIdOut)) {
         return false;
     }
     nsCOMPtr<nsIDOMWindowUtils> utils = GetDOMWindowUtils(aContent);
     return utils && (utils->GetPresShellId(aPresShellIdOut) == NS_OK);
 }
 
 }
 }
--- a/widget/xpwidgets/PuppetWidget.h
+++ b/widget/xpwidgets/PuppetWidget.h
@@ -128,24 +128,22 @@ public:
   NS_IMETHOD CaptureRollupEvents(nsIRollupListener* aListener,
                                  bool aDoCapture)
   { return NS_ERROR_UNEXPECTED; }
 
   //
   // nsBaseWidget methods we override
   //
 
-  // Documents loaded in child processes are always subdocuments of
-  // other docs in an ancestor process.  To ensure that the
-  // backgrounds of those documents are painted like those of
-  // same-process subdocuments, we force the widget here to be
-  // transparent, which in turn will cause layout to use a transparent
-  // backstop background color.
+  // Force documents to be opaque so that we don't have to retain
+  // the content behind a remote frame. This is a large memory saving
+  // for HiDPI b2g devices. When we need apps to be transparent we
+  // will need to provide a way for them to opt into it.
   virtual nsTransparencyMode GetTransparencyMode() MOZ_OVERRIDE
-  { return eTransparencyTransparent; }
+  { return eTransparencyOpaque; }
 
   virtual LayerManager*
   GetLayerManager(PLayerTransactionChild* aShadowManager = nullptr,
                   LayersBackend aBackendHint = mozilla::layers::LAYERS_NONE,
                   LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT,
                   bool* aAllowRetaining = nullptr);
   virtual gfxASurface*      GetThebesSurface();