Merge m-c to inbound.
authorRyan VanderMeulen <ryanvm@gmail.com>
Tue, 11 Jun 2013 10:41:06 -0400
changeset 146132 8ac5d1f98b21a34b28cd3d7d655d9c360382bfcd
parent 146131 436c88ed1e5efbd4b1a18320f011cf438082223e (current diff)
parent 146115 c35fea511dc6244749c49f919a33926f34d759c0 (diff)
child 146133 22b38f0b80849de065388ef3f418e43a518ce0fa
push id2697
push userbbajaj@mozilla.com
push dateMon, 05 Aug 2013 18:49:53 +0000
treeherdermozilla-beta@dfec938c7b63 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
milestone24.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Merge m-c to inbound.
--- a/content/canvas/src/CanvasRenderingContext2D.cpp
+++ b/content/canvas/src/CanvasRenderingContext2D.cpp
@@ -102,16 +102,20 @@
 #include "GLContextProvider.h"
 #include "SurfaceTypes.h"
 #endif
 
 #ifdef XP_WIN
 #include "gfxWindowsPlatform.h"
 #endif
 
+#ifdef MOZ_WIDGET_GONK
+#include "mozilla/layers/ShadowLayers.h"
+#endif
+
 // windows.h (included by chromium code) defines this, in its infinite wisdom
 #undef DrawText
 
 using namespace mozilla;
 using namespace mozilla::CanvasUtils;
 using namespace mozilla::css;
 using namespace mozilla::gfx;
 using namespace mozilla::ipc;
@@ -778,16 +782,23 @@ CanvasRenderingContext2D::EnsureTarget()
     }
 
      if (layerManager) {
 #ifdef USE_SKIA_GPU
        if (gfxPlatform::GetPlatform()->UseAcceleratedSkiaCanvas()) {
          SurfaceCaps caps = SurfaceCaps::ForRGBA();
          caps.preserve = true;
 
+#ifdef MOZ_WIDGET_GONK
+         layers::ShadowLayerForwarder *forwarder = layerManager->AsShadowForwarder();
+         if (forwarder) {
+           caps.surfaceAllocator = static_cast<layers::ISurfaceAllocator*>(forwarder);
+         }
+#endif
+
          mGLContext = mozilla::gl::GLContextProvider::CreateOffscreen(gfxIntSize(size.width,
                                                                                  size.height),
                                                                       caps,
                                                                       mozilla::gl::GLContext::ContextFlagsNone);
 
          if (mGLContext) {
            mTarget = gfxPlatform::GetPlatform()->CreateDrawTargetForFBO(0, mGLContext, size, format);
          } else {
--- a/content/media/AudioSegment.cpp
+++ b/content/media/AudioSegment.cpp
@@ -50,17 +50,17 @@ InterleaveAndConvertBuffer(const int16_t
     for (int32_t channel = 0; channel < aChannels; ++channel) {
       float v = AudioSampleToFloat(aSourceChannels[channel][i])*aVolume;
       *output = FloatToAudioSample<int16_t>(v);
       ++output;
     }
   }
 }
 
-static void
+void
 InterleaveAndConvertBuffer(const void** aSourceChannels,
                            AudioSampleFormat aSourceFormat,
                            int32_t aLength, float aVolume,
                            int32_t aChannels,
                            AudioDataValue* aOutput)
 {
   switch (aSourceFormat) {
   case AUDIO_FORMAT_FLOAT32:
@@ -87,23 +87,63 @@ AudioSegment::ApplyVolume(float aVolume)
     ci->mVolume *= aVolume;
   }
 }
 
 static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
 static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
 
 void
+DownmixAndInterleave(const nsTArray<const void*>& aChannelData,
+                     AudioSampleFormat aSourceFormat, int32_t aDuration,
+                     float aVolume, int32_t aOutputChannels,
+                     AudioDataValue* aOutput)
+{
+  nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData;
+  nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixConversionBuffer;
+  nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixOutputBuffer;
+
+  if (aSourceFormat != AUDIO_FORMAT_FLOAT32) {
+    NS_ASSERTION(aSourceFormat == AUDIO_FORMAT_S16, "unknown format");
+    downmixConversionBuffer.SetLength(aDuration*aChannelData.Length());
+    for (uint32_t i = 0; i < aChannelData.Length(); ++i) {
+      float* conversionBuf = downmixConversionBuffer.Elements() + (i*aDuration);
+      const int16_t* sourceBuf = static_cast<const int16_t*>(aChannelData[i]);
+      for (uint32_t j = 0; j < (uint32_t)aDuration; ++j) {
+        conversionBuf[j] = AudioSampleToFloat(sourceBuf[j]);
+      }
+      channelData[i] = conversionBuf;
+    }
+  } else {
+    for (uint32_t i = 0; i < aChannelData.Length(); ++i) {
+      channelData[i] = aChannelData[i];
+    }
+  }
+
+  downmixOutputBuffer.SetLength(aDuration*aOutputChannels);
+  nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannelBuffers;
+  nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> outputChannelData;
+  outputChannelBuffers.SetLength(aOutputChannels);
+  outputChannelData.SetLength(aOutputChannels);
+  for (uint32_t i = 0; i < (uint32_t)aOutputChannels; ++i) {
+    outputChannelData[i] = outputChannelBuffers[i] =
+        downmixOutputBuffer.Elements() + aDuration*i;
+  }
+  AudioChannelsDownMix(channelData, outputChannelBuffers.Elements(),
+                       aOutputChannels, aDuration);
+  InterleaveAndConvertBuffer(outputChannelData.Elements(), AUDIO_FORMAT_FLOAT32,
+                             aDuration, aVolume, aOutputChannels, aOutput);
+}
+
+void
 AudioSegment::WriteTo(AudioStream* aOutput)
 {
   uint32_t outputChannels = aOutput->GetChannels();
   nsAutoTArray<AudioDataValue,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> buf;
   nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> channelData;
-  nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixConversionBuffer;
-  nsAutoTArray<float,AUDIO_PROCESSING_FRAMES*GUESS_AUDIO_CHANNELS> downmixOutputBuffer;
 
   for (ChunkIterator ci(*this); !ci.IsEnded(); ci.Next()) {
     AudioChunk& c = *ci;
     TrackTicks offset = 0;
     while (offset < c.mDuration) {
       TrackTicks durationTicks =
         std::min<TrackTicks>(c.mDuration - offset, AUDIO_PROCESSING_FRAMES);
       if (uint64_t(outputChannels)*durationTicks > INT32_MAX || offset > INT32_MAX) {
@@ -122,44 +162,18 @@ AudioSegment::WriteTo(AudioStream* aOutp
         if (channelData.Length() < outputChannels) {
           // Up-mix. Note that this might actually make channelData have more
           // than outputChannels temporarily.
           AudioChannelsUpMix(&channelData, outputChannels, gZeroChannel);
         }
 
         if (channelData.Length() > outputChannels) {
           // Down-mix.
-          if (c.mBufferFormat != AUDIO_FORMAT_FLOAT32) {
-            NS_ASSERTION(c.mBufferFormat == AUDIO_FORMAT_S16, "unknown format");
-            downmixConversionBuffer.SetLength(duration*channelData.Length());
-            for (uint32_t i = 0; i < channelData.Length(); ++i) {
-              float* conversionBuf = downmixConversionBuffer.Elements() + (i*duration);
-              const int16_t* sourceBuf = static_cast<const int16_t*>(channelData[i]);
-              for (uint32_t j = 0; j < duration; ++j) {
-                conversionBuf[j] = AudioSampleToFloat(sourceBuf[j]);
-              }
-              channelData[i] = conversionBuf;
-            }
-          }
-
-          downmixOutputBuffer.SetLength(duration*outputChannels);
-          nsAutoTArray<float*,GUESS_AUDIO_CHANNELS> outputChannelBuffers;
-          nsAutoTArray<const void*,GUESS_AUDIO_CHANNELS> outputChannelData;
-          outputChannelBuffers.SetLength(outputChannels);
-          outputChannelData.SetLength(outputChannels);
-          for (uint32_t i = 0; i < outputChannels; ++i) {
-            outputChannelData[i] = outputChannelBuffers[i] =
-                downmixOutputBuffer.Elements() + duration*i;
-          }
-          AudioChannelsDownMix(channelData, outputChannelBuffers.Elements(),
-                               outputChannels, duration);
-          InterleaveAndConvertBuffer(outputChannelData.Elements(), AUDIO_FORMAT_FLOAT32,
-                                     duration, c.mVolume,
-                                     outputChannels,
-                                     buf.Elements());
+          DownmixAndInterleave(channelData, c.mBufferFormat, duration,
+                               c.mVolume, channelData.Length(), buf.Elements());
         } else {
           InterleaveAndConvertBuffer(channelData.Elements(), c.mBufferFormat,
                                      duration, c.mVolume,
                                      outputChannels,
                                      buf.Elements());
         }
       } else {
         // Assumes that a bit pattern of zeroes == 0.0f
--- a/content/media/AudioSegment.h
+++ b/content/media/AudioSegment.h
@@ -20,16 +20,30 @@ class AudioStream;
  */
 const int GUESS_AUDIO_CHANNELS = 2;
 
 // We ensure that the graph advances in steps that are multiples of the Web
 // Audio block size
 const uint32_t WEBAUDIO_BLOCK_SIZE_BITS = 7;
 const uint32_t WEBAUDIO_BLOCK_SIZE = 1 << WEBAUDIO_BLOCK_SIZE_BITS;
 
+void InterleaveAndConvertBuffer(const void** aSourceChannels,
+                                AudioSampleFormat aSourceFormat,
+                                int32_t aLength, float aVolume,
+                                int32_t aChannels,
+                                AudioDataValue* aOutput);
+/**
+ * Down-mix audio channels, and interleave the channel data. A total of
+ * aOutputChannels*aDuration interleaved samples will be stored into aOutput.
+ */
+void DownmixAndInterleave(const nsTArray<const void*>& aChannelData,
+                          AudioSampleFormat aSourceFormat, int32_t aDuration,
+                          float aVolume, int32_t aOutputChannels,
+                          AudioDataValue* aOutput);
+
 /**
  * An AudioChunk represents a multi-channel buffer of audio samples.
  * It references an underlying ThreadSharedObject which manages the lifetime
  * of the buffer. An AudioChunk maintains its own duration and channel data
  * pointers so it can represent a subinterval of a buffer without copying.
  * An AudioChunk can store its individual channels anywhere; it maintains
  * separate pointers to each channel's buffer.
  */
--- a/content/media/MediaSegment.h
+++ b/content/media/MediaSegment.h
@@ -204,16 +204,20 @@ public:
     void Next() { ++mIndex; }
     Chunk& operator*() { return mSegment.mChunks[mIndex]; }
     Chunk* operator->() { return &mSegment.mChunks[mIndex]; }
   private:
     MediaSegmentBase<C, Chunk>& mSegment;
     uint32_t mIndex;
   };
 
+  void RemoveLeading(TrackTicks aDuration)
+  {
+    RemoveLeading(aDuration, 0);
+  }
 protected:
   MediaSegmentBase(Type aType) : MediaSegment(aType) {}
 
   /**
    * Appends the contents of aSource to this segment, clearing aSource.
    */
   void AppendFromInternal(MediaSegmentBase<C, Chunk>* aSource)
   {
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/ContainerWriter.h
@@ -0,0 +1,56 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef ContainerWriter_h_
+#define ContainerWriter_h_
+
+#include "nsTArray.h"
+#include "nsAutoPtr.h"
+
+namespace mozilla {
+/**
+ * ContainerWriter packs encoded track data into a specific media container.
+ */
+class ContainerWriter {
+public:
+  ContainerWriter()
+    : mInitialized(false)
+  {}
+  virtual ~ContainerWriter() {}
+
+  enum {
+    END_OF_STREAM = 1 << 0
+  };
+
+  /**
+   * Writes encoded track data from aBuffer to a packet, and insert this packet
+   * into the internal stream of container writer. aDuration is the playback
+   * duration of this packet in number of samples. aFlags is true with
+   * END_OF_STREAM if this is the last packet of track.
+   * Currently, WriteEncodedTrack doesn't support multiple tracks.
+   */
+  virtual nsresult WriteEncodedTrack(const nsTArray<uint8_t>& aBuffer,
+                                     int aDuration, uint32_t aFlags = 0) = 0;
+
+  enum {
+    FLUSH_NEEDED = 1 << 0
+  };
+
+  /**
+   * Copies the final container data to a buffer if it has accumulated enough
+   * packets from WriteEncodedTrack. This buffer of data is appended to
+   * aOutputBufs, and existing elements of aOutputBufs should not be modified.
+   * aFlags is true with FLUSH_NEEDED will force OggWriter to flush an ogg page
+   * even it is not full, and copy these container data to a buffer for
+   * aOutputBufs to append.
+   */
+  virtual nsresult GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
+                                    uint32_t aFlags = 0) = 0;
+
+protected:
+  bool mInitialized;
+};
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/Makefile.in
@@ -0,0 +1,19 @@
+# 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/.
+
+DEPTH     = @DEPTH@
+topsrcdir = @top_srcdir@
+srcdir    = @srcdir@
+VPATH     = @srcdir@
+
+include $(DEPTH)/config/autoconf.mk
+
+LIBRARY_NAME = gkconencoder_s
+LIBXUL_LIBRARY = 1
+FAIL_ON_WARNINGS := 1
+
+
+FORCE_STATIC_LIB = 1
+
+include $(topsrcdir)/config/rules.mk
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/MediaEncoder.cpp
@@ -0,0 +1,242 @@
+/* -*- Mode: C++; 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/. */
+#include "MediaEncoder.h"
+#include "MediaDecoder.h"
+#ifdef MOZ_OGG
+#include "OggWriter.h"
+#endif
+#ifdef MOZ_OPUS
+#include "OpusTrackEncoder.h"
+#endif
+
+#ifdef MOZ_WIDGET_GONK
+#include <android/log.h>
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
+#else
+#define LOG(args,...)
+#endif
+
+namespace mozilla {
+
+#define TRACK_BUFFER_LEN 8192
+
+namespace {
+
+template <class String>
+static bool
+TypeListContains(char const *const * aTypes, const String& aType)
+{
+  for (int32_t i = 0; aTypes[i]; ++i) {
+    if (aType.EqualsASCII(aTypes[i]))
+      return true;
+  }
+  return false;
+}
+
+#ifdef MOZ_OGG
+// The recommended mime-type for Ogg Opus files is audio/ogg.
+// See http://wiki.xiph.org/OggOpus for more details.
+static const char* const gOggTypes[2] = {
+  "audio/ogg",
+  nullptr
+};
+
+static bool
+IsOggType(const nsAString& aType)
+{
+  if (!MediaDecoder::IsOggEnabled()) {
+    return false;
+  }
+
+  return TypeListContains(gOggTypes, aType);
+}
+#endif
+} //anonymous namespace
+
+void
+MediaEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
+                                       TrackID aID,
+                                       TrackRate aTrackRate,
+                                       TrackTicks aTrackOffset,
+                                       uint32_t aTrackEvents,
+                                       const MediaSegment& aQueuedMedia)
+{
+  // Process the incoming raw track data from MediaStreamGraph, called on the
+  // thread of MediaStreamGraph.
+  if (aQueuedMedia.GetType() == MediaSegment::AUDIO) {
+    mAudioEncoder->NotifyQueuedTrackChanges(aGraph, aID, aTrackRate,
+                                            aTrackOffset, aTrackEvents,
+                                            aQueuedMedia);
+
+  } else {
+    // Type video is not supported for now.
+  }
+}
+
+void
+MediaEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
+{
+  // In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
+  LOG("NotifyRemoved in [MediaEncoder].");
+  mAudioEncoder->NotifyRemoved(aGraph);
+}
+
+/* static */
+already_AddRefed<MediaEncoder>
+MediaEncoder::CreateEncoder(const nsAString& aMIMEType)
+{
+  nsAutoPtr<ContainerWriter> writer;
+  nsAutoPtr<AudioTrackEncoder> audioEncoder;
+  nsAutoPtr<VideoTrackEncoder> videoEncoder;
+  nsRefPtr<MediaEncoder> encoder;
+
+  if (aMIMEType.IsEmpty()) {
+    // TODO: Should pick out a default container+codec base on the track
+    //       coming from MediaStreamGraph. For now, just default to Ogg+Opus.
+    const_cast<nsAString&>(aMIMEType) = NS_LITERAL_STRING("audio/ogg");
+  }
+
+  bool isAudioOnly = FindInReadable(NS_LITERAL_STRING("audio/"), aMIMEType);
+#ifdef MOZ_OGG
+  if (IsOggType(aMIMEType)) {
+    writer = new OggWriter();
+    if (!isAudioOnly) {
+      // Initialize the videoEncoder.
+    }
+#ifdef MOZ_OPUS
+    audioEncoder = new OpusTrackEncoder();
+#endif
+  }
+#endif
+  // If the given mime-type is video but fail to create the video encoder.
+  if (!isAudioOnly) {
+    NS_ENSURE_TRUE(videoEncoder, nullptr);
+  }
+
+  // Return null if we fail to create the audio encoder.
+  NS_ENSURE_TRUE(audioEncoder, nullptr);
+
+  encoder = new MediaEncoder(writer.forget(), audioEncoder.forget(),
+                             videoEncoder.forget(), aMIMEType);
+
+
+  return encoder.forget();
+}
+
+/**
+ * GetEncodedData() runs as a state machine, starting with mState set to
+ * ENCODE_HEADER, the procedure should be as follow:
+ *
+ * While non-stop
+ *   If mState is ENCODE_HEADER
+ *     Create the header from audio/video encoder
+ *     If a header is generated
+ *       Insert header data into the container stream of writer
+ *       Force copied the final container data from writer
+ *       Return the copy of final container data
+ *     Else
+ *       Set mState to ENCODE_TRACK
+ *
+ *   If mState is ENCODE_TRACK
+ *     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
+ *     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_HEADER: {
+      nsTArray<uint8_t> buffer;
+      nsresult rv = mAudioEncoder->GetHeader(&buffer);
+      if (NS_FAILED(rv)) {
+        // Encoding might be canceled.
+        mState = ENCODE_DONE;
+        break;
+      }
+
+      if (!buffer.IsEmpty()) {
+        rv = mWriter->WriteEncodedTrack(buffer, 0);
+        if (NS_FAILED(rv)) {
+          LOG("ERROR! Fail to write header to the media container.");
+          mState = ENCODE_DONE;
+          break;
+        }
+
+        rv = mWriter->GetContainerData(aOutputBufs,
+                                       ContainerWriter::FLUSH_NEEDED);
+        if (NS_SUCCEEDED(rv)) {
+          // Successfully get the copy of final container data from writer.
+          reloop = false;
+          break;
+        }
+      } else {
+        // No more headers, starts to encode tracks.
+        mState = ENCODE_TRACK;
+      }
+      break;
+    }
+
+    case ENCODE_TRACK: {
+      nsTArray<uint8_t> buffer;
+      int encodedDuration = 0;
+      nsresult rv = mAudioEncoder->GetEncodedTrack(&buffer, encodedDuration);
+      if (NS_FAILED(rv)) {
+        // Encoding might be canceled.
+        LOG("ERROR! Fail to get encoded data from encoder.");
+        mState = ENCODE_DONE;
+        break;
+      }
+
+      rv = mWriter->WriteEncodedTrack(buffer, encodedDuration,
+                                      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;
+        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.
+        reloop = false;
+        break;
+      }
+
+      mState = (mAudioEncoder->IsEncodingComplete()) ? ENCODE_DONE : ENCODE_TRACK;
+      break;
+    }
+
+    case ENCODE_DONE:
+      LOG("MediaEncoder has been shutdown.");
+      mShutdown = true;
+      reloop = false;
+      break;
+
+    default:
+      MOZ_NOT_REACHED("Invalid encode state");
+      break;
+    }
+  }
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/MediaEncoder.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef MediaEncoder_h_
+#define MediaEncoder_h_
+
+#include "mozilla/DebugOnly.h"
+#include "TrackEncoder.h"
+#include "ContainerWriter.h"
+#include "MediaStreamGraph.h"
+
+namespace mozilla {
+
+/**
+ * MediaEncoder is the framework of encoding module, it controls and manages
+ * procedures between ContainerWriter and TrackEncoder. ContainerWriter packs
+ * the encoded track data with a specific container (e.g. ogg, mp4).
+ * AudioTrackEncoder and VideoTrackEncoder are subclasses of TrackEncoder, and
+ * are responsible for encoding raw data coming from MediaStreamGraph.
+ *
+ * Also, MediaEncoder is a type of MediaStreamListener, it starts to receive raw
+ * segments after itself is added to the source stream. In the mean time,
+ * encoded track data is pulled by its owner periodically on a worker thread. A
+ * reentrant monitor is used to protect the push and pull of resource.
+ *
+ * MediaEncoder is designed to be a passive component, neither it owns nor in
+ * charge of managing threads. However, a monitor is used in function
+ * TrackEncoder::GetEncodedTrack() for the purpose of thread safety (e.g.
+ * between callbacks of MediaStreamListener and others), a call to this function
+ * might block. Therefore, MediaEncoder should not run on threads that forbid
+ * blocking, such as main thread or I/O thread.
+ *
+ * For example, an usage from MediaRecorder of this component would be:
+ * 1) Create an encoder with a valid MIME type.
+ *    => encoder = MediaEncoder::CreateEncoder(aMIMEType);
+ *    It then generate a ContainerWriter according to the MIME type, and an
+ *    AudioTrackEncoder (or a VideoTrackEncoder too) associated with the media
+ *    type.
+ *
+ * 2) Dispatch the task GetEncodedData() to a worker thread.
+ *
+ * 3) To start encoding, add this component to its source stream.
+ *    => sourceStream->AddListener(encoder);
+ *
+ * 4) To stop encoding, remove this component from its source stream.
+ *    => sourceStream->RemoveListener(encoder);
+ */
+class MediaEncoder : public MediaStreamListener
+{
+public :
+  enum {
+    ENCODE_HEADER,
+    ENCODE_TRACK,
+    ENCODE_DONE,
+  };
+
+  MediaEncoder(ContainerWriter* aWriter,
+               AudioTrackEncoder* aAudioEncoder,
+               VideoTrackEncoder* aVideoEncoder,
+               const nsAString& aMIMEType)
+    : mWriter(aWriter)
+    , mAudioEncoder(aAudioEncoder)
+    , mVideoEncoder(aVideoEncoder)
+    , mMIMEType(aMIMEType)
+    , mState(MediaEncoder::ENCODE_HEADER)
+    , mShutdown(false)
+  {}
+
+  ~MediaEncoder() {};
+
+  /**
+   * Notified by the control loop of MediaStreamGraph; aQueueMedia is the raw
+   * track data in form of MediaSegment.
+   */
+  virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
+                                        TrackRate aTrackRate,
+                                        TrackTicks aTrackOffset,
+                                        uint32_t aTrackEvents,
+                                        const MediaSegment& aQueuedMedia);
+
+  /**
+   * Notified the stream is being removed.
+   */
+  virtual void NotifyRemoved(MediaStreamGraph* aGraph);
+
+  /**
+   * Creates an encoder with a given MIME type. Returns null if we are unable
+   * to create the encoder. For now, default aMIMEType to "audio/ogg" and use
+   * Ogg+Opus if it is empty.
+   */
+  static already_AddRefed<MediaEncoder> CreateEncoder(const nsAString& aMIMEType);
+
+  /**
+   * Encodes the raw track data and returns the final container data. Assuming
+   * it is called on a single worker thread. The buffer of container data is
+   * allocated in ContainerWriter::GetContainerData(), and is appended to
+   * aOutputBufs. aMIMEType is the valid mime-type of this returned container
+   * data.
+   */
+  void GetEncodedData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
+                      nsAString& aMIMEType);
+
+  /**
+   * Return true if MediaEncoder has been shutdown. Reasons are encoding
+   * complete, encounter an error, or being canceled by its caller.
+   */
+  bool IsShutdown()
+  {
+    return mShutdown;
+  }
+
+  /**
+   * Cancel the encoding, and wakes up the lock of reentrant monitor in encoder.
+   */
+  void Cancel()
+  {
+    if (mAudioEncoder) {
+      mAudioEncoder->NotifyCancel();
+    }
+  }
+
+private:
+  nsAutoPtr<ContainerWriter> mWriter;
+  nsAutoPtr<AudioTrackEncoder> mAudioEncoder;
+  nsAutoPtr<VideoTrackEncoder> mVideoEncoder;
+  nsString mMIMEType;
+  int mState;
+  bool mShutdown;
+};
+
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/OpusTrackEncoder.cpp
@@ -0,0 +1,332 @@
+/* -*- Mode: C++; 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/. */
+#include "OpusTrackEncoder.h"
+#include "nsString.h"
+
+#include <opus/opus.h>
+
+#undef LOG
+#ifdef MOZ_WIDGET_GONK
+#include <android/log.h>
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
+#else
+#define LOG(args, ...)
+#endif
+
+namespace mozilla {
+
+// http://www.opus-codec.org/docs/html_api-1.0.2/group__opus__encoder.html
+// In section "opus_encoder_init", channels must be 1 or 2 of input signal.
+static const int MAX_CHANNELS = 2;
+
+// A maximum data bytes for Opus to encode.
+static const int MAX_DATA_BYTES = 4096;
+
+// http://tools.ietf.org/html/draft-ietf-codec-oggopus-00#section-4
+// Second paragraph, " The granule position of an audio data page is in units
+// of PCM audio samples at a fixed rate of 48 kHz."
+static const int kOpusSamplingRate = 48000;
+
+// The duration of an Opus frame, and it must be 2.5, 5, 10, 20, 40 or 60 ms.
+static const int kFrameDurationMs  = 20;
+
+namespace {
+
+// An endian-neutral serialization of integers. Serializing T in little endian
+// format to aOutput, where T is a 16 bits or 32 bits integer.
+template<typename T>
+static void
+SerializeToBuffer(T aValue, nsTArray<uint8_t>* aOutput)
+{
+  for (uint32_t i = 0; i < sizeof(T); i++) {
+    aOutput->AppendElement((uint8_t)(0x000000ff & (aValue >> (i * 8))));
+  }
+}
+
+static inline void
+SerializeToBuffer(const nsCString& aComment, nsTArray<uint8_t>* aOutput)
+{
+  // Format of serializing a string to buffer is, the length of string (32 bits,
+  // little endian), and the string.
+  SerializeToBuffer((uint32_t)(aComment.Length()), aOutput);
+  aOutput->AppendElements(aComment.get(), aComment.Length());
+}
+
+
+static void
+SerializeOpusIdHeader(uint8_t aChannelCount, uint16_t aPreskip,
+                      uint32_t aInputSampleRate, nsTArray<uint8_t>* aOutput)
+{
+  // The magic signature, null terminator has to be stripped off from strings.
+  static const uint8_t magic[9] = "OpusHead";
+  memcpy(aOutput->AppendElements(sizeof(magic) - 1), magic, sizeof(magic) - 1);
+
+  // The version, must always be 1 (8 bits, unsigned).
+  aOutput->AppendElement(1);
+
+  // Number of output channels (8 bits, unsigned).
+  aOutput->AppendElement(aChannelCount);
+
+  // Number of samples (at 48 kHz) to discard from the decoder output when
+  // starting playback (16 bits, unsigned, little endian).
+  SerializeToBuffer(aPreskip, aOutput);
+
+  // The sampling rate of input source (32 bits, unsigned, little endian).
+  SerializeToBuffer(aInputSampleRate, aOutput);
+
+  // Output gain, an encoder should set this field to zero (16 bits, signed,
+  // little endian).
+  SerializeToBuffer((int16_t)0, aOutput);
+
+  // Channel mapping family. Family 0 allows only 1 or 2 channels (8 bits,
+  // unsigned).
+  aOutput->AppendElement(0);
+}
+
+static void
+SerializeOpusCommentHeader(const nsCString& aVendor,
+                           const nsTArray<nsCString>& aComments,
+                           nsTArray<uint8_t>* aOutput)
+{
+  // The magic signature, null terminator has to be stripped off.
+  static const uint8_t magic[9] = "OpusTags";
+  memcpy(aOutput->AppendElements(sizeof(magic) - 1), magic, sizeof(magic) - 1);
+
+  // The vendor; Should append in the following order:
+  // vendor string length (32 bits, unsigned, little endian)
+  // vendor string.
+  SerializeToBuffer(aVendor, aOutput);
+
+  // Add comments; Should append in the following order:
+  // comment list length (32 bits, unsigned, little endian)
+  // comment #0 string length (32 bits, unsigned, little endian)
+  // comment #0 string
+  // comment #1 string length (32 bits, unsigned, little endian)
+  // comment #1 string ...
+  SerializeToBuffer((uint32_t)aComments.Length(), aOutput);
+  for (uint32_t i = 0; i < aComments.Length(); ++i) {
+    SerializeToBuffer(aComments[i], aOutput);
+  }
+}
+
+}  // Anonymous namespace.
+
+OpusTrackEncoder::OpusTrackEncoder()
+  : AudioTrackEncoder()
+  , mEncoderState(ID_HEADER)
+  , mEncoder(nullptr)
+  , mSourceSegment(new AudioSegment())
+  , mLookahead(0)
+{
+}
+
+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;
+
+  // 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 (!((aSamplingRate >= 8000) && (kOpusSamplingRate / aSamplingRate) *
+         aSamplingRate == kOpusSamplingRate)) {
+    LOG("[Opus] Error! The source sample rate should be greater than 8000 and"
+        " divides 48000 evenly.");
+    return NS_ERROR_FAILURE;
+  }
+  mSamplingRate = aSamplingRate;
+
+  int error = 0;
+  mEncoder = opus_encoder_create(mSamplingRate, mChannels,
+                                 OPUS_APPLICATION_AUDIO, &error);
+
+  mInitialized = (error == OPUS_OK);
+
+  mReentrantMonitor.NotifyAll();
+
+  return error == OPUS_OK ? NS_OK : NS_ERROR_FAILURE;
+}
+
+int
+OpusTrackEncoder::GetPacketDuration()
+{
+  return mSamplingRate * kFrameDurationMs / 1000;
+}
+
+nsresult
+OpusTrackEncoder::GetHeader(nsTArray<uint8_t>* aOutput)
+{
+  {
+    // Wait if mEncoder is not initialized.
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    while (!mCanceled && !mEncoder) {
+      mReentrantMonitor.Wait();
+    }
+  }
+
+  if (mCanceled) {
+    return NS_ERROR_FAILURE;
+  }
+
+  switch (mEncoderState) {
+  case ID_HEADER:
+  {
+    mLookahead = 0;
+    int error = opus_encoder_ctl(mEncoder, OPUS_GET_LOOKAHEAD(&mLookahead));
+    if (error != OPUS_OK) {
+      mLookahead = 0;
+    }
+
+    // The ogg time stamping and pre-skip is always timed at 48000.
+    SerializeOpusIdHeader(mChannels, mLookahead*(kOpusSamplingRate/mSamplingRate),
+                          mSamplingRate, aOutput);
+
+    mEncoderState = COMMENT_HEADER;
+    break;
+  }
+  case COMMENT_HEADER:
+  {
+    nsCString vendor;
+    vendor.AppendASCII(opus_get_version_string());
+
+    nsTArray<nsCString> comments;
+    comments.AppendElement(NS_LITERAL_CSTRING("ENCODER=Mozilla" MOZ_APP_UA_VERSION));
+
+    SerializeOpusCommentHeader(vendor, comments, aOutput);
+
+    mEncoderState = DATA;
+    break;
+  }
+  case DATA:
+    // No more headers.
+    break;
+  default:
+    MOZ_NOT_REACHED("Invalid state");
+    break;
+  }
+  return NS_OK;
+}
+
+nsresult
+OpusTrackEncoder::GetEncodedTrack(nsTArray<uint8_t>* aOutput,
+                                  int &aOutputDuration)
+{
+  {
+    // Move all the samples from mRawSegment to mSourceSegment. We only hold
+    // the monitor in this block.
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+    // Wait if mEncoder is not initialized, or when not enough raw data, but is
+    // not the end of stream nor is being canceled.
+    while (!mCanceled && (!mEncoder || (mRawSegment->GetDuration() +
+           mSourceSegment->GetDuration() < GetPacketDuration() &&
+           !mEndOfStream))) {
+      mReentrantMonitor.Wait();
+    }
+
+    if (mCanceled) {
+      return NS_ERROR_FAILURE;
+    }
+
+    mSourceSegment->AppendFrom(mRawSegment);
+
+    // Pad |mLookahead| samples to the end of source stream to prevent lost of
+    // original data, the pcm duration will be calculated at rate 48K later.
+    if (mEndOfStream) {
+      mSourceSegment->AppendNullData(mLookahead);
+    }
+  }
+
+  // Start encoding data.
+  nsAutoTArray<AudioDataValue, 9600> pcm;
+  pcm.SetLength(GetPacketDuration() * mChannels);
+  AudioSegment::ChunkIterator iter(*mSourceSegment);
+  int frameCopied = 0;
+  while (!iter.IsEnded() && frameCopied < GetPacketDuration()) {
+    AudioChunk chunk = *iter;
+
+    // Chunk to the required frame size.
+    int frameToCopy = chunk.GetDuration();
+    if (frameCopied + frameToCopy > GetPacketDuration()) {
+      frameToCopy = GetPacketDuration() - frameCopied;
+    }
+
+    if (!chunk.IsNull()) {
+      // Append the interleaved data to the end of pcm buffer.
+      InterleaveTrackData(chunk, frameToCopy, mChannels,
+                          pcm.Elements() + frameCopied);
+    } else {
+      for (int i = 0; i < frameToCopy * mChannels; i++) {
+        pcm.AppendElement(0);
+      }
+    }
+
+    frameCopied += frameToCopy;
+    iter.Next();
+  }
+
+  // The ogg time stamping and pre-skip is always timed at 48000.
+  aOutputDuration = frameCopied * (kOpusSamplingRate / mSamplingRate);
+
+  // Remove the raw data which has been pulled to pcm buffer.
+  // The value of frameCopied should equal to (or smaller than, if eos)
+  // GetPacketDuration().
+  mSourceSegment->RemoveLeading(frameCopied);
+
+  // Has reached the end of input stream and all queued data has pulled for
+  // encoding.
+  if (mSourceSegment->GetDuration() == 0 && mEndOfStream) {
+    mDoneEncoding = true;
+    LOG("[Opus] Done encoding.");
+  }
+
+  // Append null data to pcm buffer if the leftover data is not enough for
+  // opus encoder.
+  if (frameCopied < GetPacketDuration() && mEndOfStream) {
+    for (int i = frameCopied * mChannels; i < GetPacketDuration() * mChannels; i++) {
+      pcm.AppendElement(0);
+    }
+  }
+
+  // Encode the data with Opus Encoder.
+  aOutput->SetLength(MAX_DATA_BYTES);
+  // result is returned as opus error code if it is negative.
+  int result = 0;
+#ifdef MOZ_SAMPLE_TYPE_S16
+  const opus_int16* pcmBuf = static_cast<opus_int16*>(pcm.Elements());
+  result = opus_encode(mEncoder, pcmBuf, GetPacketDuration(),
+                       aOutput->Elements(), MAX_DATA_BYTES);
+#else
+  const float* pcmBuf = static_cast<float*>(pcm.Elements());
+  result = opus_encode_float(mEncoder, pcmBuf, GetPacketDuration(),
+                             aOutput->Elements(), MAX_DATA_BYTES);
+#endif
+  aOutput->SetLength(result >= 0 ? result : 0);
+
+  if (result < 0) {
+    LOG("[Opus] Fail to encode data! Result: %s.", opus_strerror(result));
+  }
+
+  return result >= 0 ? NS_OK : NS_ERROR_FAILURE;
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/OpusTrackEncoder.h
@@ -0,0 +1,60 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef OpusTrackEncoder_h_
+#define OpusTrackEncoder_h_
+
+#include "TrackEncoder.h"
+#include "nsCOMPtr.h"
+
+struct OpusEncoder;
+
+namespace mozilla {
+
+class OpusTrackEncoder : public AudioTrackEncoder
+{
+public:
+  OpusTrackEncoder();
+  virtual ~OpusTrackEncoder();
+
+  nsresult GetHeader(nsTArray<uint8_t>* aOutput) MOZ_OVERRIDE;
+
+  nsresult GetEncodedTrack(nsTArray<uint8_t>* aOutput, int &aOutputDuration) MOZ_OVERRIDE;
+
+protected:
+  int GetPacketDuration() MOZ_OVERRIDE;
+
+  nsresult Init(int aChannels, int aSamplingRate) MOZ_OVERRIDE;
+
+private:
+  enum {
+    ID_HEADER,
+    COMMENT_HEADER,
+    DATA
+  } mEncoderState;
+
+  /**
+   * The Opus encoder from libopus.
+   */
+  OpusEncoder* mEncoder;
+
+  /**
+   * A local segment queue which stores the raw segments. Opus encoder only
+   * takes GetPacketDuration() samples from mSourceSegment in every encoding
+   * cycle, thus it needs to store the raw track data.
+   */
+  nsAutoPtr<AudioSegment> mSourceSegment;
+
+  /**
+   * Total samples of delay added by codec, can be queried by the encoder. From
+   * the perspective of decoding, real data begins this many samples late, so
+   * the encoder needs to append this many null samples to the end of stream,
+   * in order to align the time of input and output.
+   */
+  int mLookahead;
+};
+
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/TrackEncoder.cpp
@@ -0,0 +1,117 @@
+/* -*- Mode: C++; 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/. */
+#include "TrackEncoder.h"
+#include "MediaStreamGraph.h"
+#include "AudioChannelFormat.h"
+
+#undef LOG
+#ifdef MOZ_WIDGET_GONK
+#include <android/log.h>
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediakEncoder", ## args);
+#else
+#define LOG(args, ...)
+#endif
+
+namespace mozilla {
+
+#define MAX_FRAMES_TO_DROP  48000
+
+void
+AudioTrackEncoder::NotifyQueuedTrackChanges(MediaStreamGraph* aGraph,
+                                            TrackID aID,
+                                            TrackRate aTrackRate,
+                                            TrackTicks aTrackOffset,
+                                            uint32_t aTrackEvents,
+                                            const MediaSegment& aQueuedMedia)
+{
+  AudioSegment* audio = const_cast<AudioSegment*>
+                        (static_cast<const AudioSegment*>(&aQueuedMedia));
+
+  // Check and initialize parameters for codec encoder.
+  if (!mInitialized) {
+    AudioSegment::ChunkIterator iter(*audio);
+    while (!iter.IsEnded()) {
+      AudioChunk chunk = *iter;
+      if (chunk.mBuffer) {
+        Init(chunk.mChannelData.Length(), aTrackRate);
+        break;
+      }
+      iter.Next();
+    }
+  }
+
+  // Append and consume this raw segment.
+  AppendAudioSegment(audio);
+
+  // The stream has stopped and reached the end of track.
+  if (aTrackEvents == MediaStreamListener::TRACK_EVENT_ENDED) {
+    LOG("[AudioTrackEncoder]: Receive TRACK_EVENT_ENDED .");
+    NotifyEndOfStream();
+  }
+}
+
+void
+AudioTrackEncoder::NotifyRemoved(MediaStreamGraph* aGraph)
+{
+  // In case that MediaEncoder does not receive a TRACK_EVENT_ENDED event.
+  LOG("[AudioTrackEncoder]: NotifyRemoved.");
+  NotifyEndOfStream();
+}
+
+nsresult
+AudioTrackEncoder::AppendAudioSegment(MediaSegment* aSegment)
+{
+  // Drop the in-coming segment if buffer(mRawSegment) is overflow.
+  ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+
+  AudioSegment* audio = static_cast<AudioSegment*>(aSegment);
+  AudioSegment::ChunkIterator iter(*audio);
+
+  if (mRawSegment->GetDuration() < MAX_FRAMES_TO_DROP) {
+    while(!iter.IsEnded()) {
+      AudioChunk chunk = *iter;
+      if (chunk.mBuffer) {
+        mRawSegment->AppendAndConsumeChunk(&chunk);
+      }
+      iter.Next();
+    }
+    if (mRawSegment->GetDuration() >= GetPacketDuration()) {
+      mReentrantMonitor.NotifyAll();
+    }
+  }
+#ifdef DEBUG
+  else {
+    LOG("[AudioTrackEncoder]: A segment has dropped!");
+  }
+#endif
+
+  return NS_OK;
+}
+
+static const int AUDIO_PROCESSING_FRAMES = 640; /* > 10ms of 48KHz audio */
+static const uint8_t gZeroChannel[MAX_AUDIO_SAMPLE_SIZE*AUDIO_PROCESSING_FRAMES] = {0};
+
+void
+AudioTrackEncoder::InterleaveTrackData(AudioChunk& aChunk,
+                                       int32_t aDuration,
+                                       uint32_t aOutputChannels,
+                                       AudioDataValue* aOutput)
+{
+  if (aChunk.mChannelData.Length() < aOutputChannels) {
+    // Up-mix. This might make the mChannelData have more than aChannels.
+    AudioChannelsUpMix(&aChunk.mChannelData, aOutputChannels, gZeroChannel);
+  }
+
+  if (aChunk.mChannelData.Length() > aOutputChannels) {
+    DownmixAndInterleave(aChunk.mChannelData, aChunk.mBufferFormat, aDuration,
+                         aChunk.mVolume, mChannels, aOutput);
+  } else {
+    InterleaveAndConvertBuffer(aChunk.mChannelData.Elements(),
+                               aChunk.mBufferFormat, aDuration, aChunk.mVolume,
+                               mChannels, aOutput);
+  }
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/TrackEncoder.h
@@ -0,0 +1,187 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef TrackEncoder_h_
+#define TrackEncoder_h_
+
+#include "mozilla/ReentrantMonitor.h"
+
+#include "AudioSegment.h"
+#include "StreamBuffer.h"
+
+namespace mozilla {
+
+class MediaStreamGraph;
+
+/**
+ * Base class of AudioTrackEncoder and VideoTrackEncoder. Lifetimes managed by
+ * MediaEncoder. Most methods can only be called on the MediaEncoder's thread,
+ * but some subclass methods can be called on other threads when noted.
+ *
+ * NotifyQueuedTrackChanges is called on subclasses of this class from the
+ * MediaStreamGraph thread, and AppendAudioSegment/AppendVideoSegment is then
+ * called to store media data in the TrackEncoder. Later on, GetEncodedTrack is
+ * called on MediaEncoder's thread to encode and retrieve the encoded data.
+ */
+class TrackEncoder
+{
+public:
+  TrackEncoder() {}
+  virtual ~TrackEncoder() {}
+
+  /**
+   * Notified by the same callbcak of MediaEncoder when it has received a track
+   * change from MediaStreamGraph. Called on the MediaStreamGraph thread.
+   */
+  virtual void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
+                                        TrackRate aTrackRate,
+                                        TrackTicks aTrackOffset,
+                                        uint32_t aTrackEvents,
+                                        const MediaSegment& aQueuedMedia) = 0;
+
+  /**
+   * Notified by the same callback of MediaEncoder when it has been removed from
+   * MediaStreamGraph. Called on the MediaStreamGraph thread.
+   */
+  virtual void NotifyRemoved(MediaStreamGraph* aGraph) = 0;
+
+  /**
+   * Creates and sets up header for a specific codec. Result data is returned
+   * in aOutput.
+   */
+  virtual nsresult GetHeader(nsTArray<uint8_t>* aOutput) = 0;
+
+  /**
+   * Encodes raw segments. Result data is returned in aOutput. aOutputDuration
+   * is the playback duration of this packet in number of samples.
+   */
+  virtual nsresult GetEncodedTrack(nsTArray<uint8_t>* aOutput,
+                                   int &aOutputDuration) = 0;
+};
+
+class AudioTrackEncoder : public TrackEncoder
+{
+public:
+  AudioTrackEncoder()
+    : TrackEncoder()
+    , mChannels(0)
+    , mSamplingRate(0)
+    , mInitialized(false)
+    , mDoneEncoding(false)
+    , mReentrantMonitor("media.AudioEncoder")
+    , mRawSegment(new AudioSegment())
+    , mEndOfStream(false)
+    , mCanceled(false)
+  {}
+
+  void NotifyQueuedTrackChanges(MediaStreamGraph* aGraph, TrackID aID,
+                                TrackRate aTrackRate,
+                                TrackTicks aTrackOffset,
+                                uint32_t aTrackEvents,
+                                const MediaSegment& aQueuedMedia) MOZ_OVERRIDE;
+
+  void NotifyRemoved(MediaStreamGraph* aGraph) MOZ_OVERRIDE;
+
+  bool IsEncodingComplete()
+  {
+    return mDoneEncoding;
+  }
+
+  /**
+   * Notifies from MediaEncoder to cancel the encoding, and wakes up
+   * mReentrantMonitor if encoder is waiting on it.
+   */
+  void NotifyCancel()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mCanceled = true;
+    mReentrantMonitor.NotifyAll();
+  }
+
+protected:
+  /**
+   * Number of samples per channel in a pcm buffer. This is also the value of
+   * frame size required by audio encoder, and mReentrantMonitor will be
+   * notified when at least this much data has been added to mRawSegment.
+   */
+  virtual int GetPacketDuration() = 0;
+
+  /**
+   * Initializes the audio encoder. The call of this method is delayed until we
+   * have received the first valid track from MediaStreamGraph, and the
+   * mReentrantMonitor will be notified if other methods is waiting for encoder
+   * to be completely initialized. This method is called on the MediaStreamGraph
+   * thread.
+   */
+  virtual nsresult Init(int aChannels, int aSamplingRate) = 0;
+
+  /**
+   * Appends and consumes track data from aSegment, this method is called on
+   * the MediaStreamGraph thread. mReentrantMonitor will be notified when at
+   * least GetPacketDuration() data has been added to mRawSegment, wake up other
+   * method which is waiting for more data from mRawSegment.
+   */
+  nsresult AppendAudioSegment(MediaSegment* aSegment);
+
+  /**
+   * Notifies the audio encoder that we have reached the end of source stream,
+   * and wakes up mReentrantMonitor if encoder is waiting for more track data.
+   */
+  void NotifyEndOfStream()
+  {
+    ReentrantMonitorAutoEnter mon(mReentrantMonitor);
+    mEndOfStream = true;
+    mReentrantMonitor.NotifyAll();
+  }
+
+  /**
+   * Interleaves the track data and stores the result into aOutput. Might need
+   * 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.
+   */
+  int mChannels;
+  int mSamplingRate;
+  bool mInitialized;
+  bool mDoneEncoding;
+
+  /**
+   * A ReentrantMonitor to protect the pushing and pulling of mRawSegment.
+   */
+  ReentrantMonitor mReentrantMonitor;
+
+  /**
+   * A segment queue of audio track data, protected by mReentrantMonitor.
+   */
+  nsAutoPtr<AudioSegment> mRawSegment;
+
+  /**
+   * True if we have received an event of TRACK_EVENT_ENDED from MediaStreamGraph,
+   * or the MediaEncoder is removed from its source stream, protected by
+   * mReentrantMonitor.
+   */
+  bool mEndOfStream;
+
+  /**
+   * True if a cancellation of encoding is sent from MediaEncoder, protected by
+   * mReentrantMonitor.
+   */
+  bool mCanceled;
+};
+
+class VideoTrackEncoder : public TrackEncoder
+{
+
+};
+
+}
+#endif
new file mode 100644
--- /dev/null
+++ b/content/media/encoder/moz.build
@@ -0,0 +1,22 @@
+# -*- 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/.
+
+MODULE = 'content'
+
+EXPORTS += [
+    'ContainerWriter.h',
+    'MediaEncoder.h',
+    'TrackEncoder.h',
+]
+
+CPP_SOURCES += [
+    'MediaEncoder.cpp',
+    'TrackEncoder.cpp',
+]
+
+if CONFIG['MOZ_OPUS']:
+    EXPORTS += ['OpusTrackEncoder.h']
+    CPP_SOURCES += ['OpusTrackEncoder.cpp']
--- a/content/media/moz.build
+++ b/content/media/moz.build
@@ -1,14 +1,16 @@
 # -*- 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/.
 
+PARALLEL_DIRS += ['encoder']
+
 PARALLEL_DIRS += ['webaudio']
 
 if CONFIG['MOZ_RAW']:
     PARALLEL_DIRS += ['raw']
 
 if CONFIG['MOZ_OGG']:
     PARALLEL_DIRS += ['ogg']
 
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/OggWriter.cpp
@@ -0,0 +1,113 @@
+/* -*- Mode: C++; 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/. */
+#include "OggWriter.h"
+
+#undef LOG
+#ifdef MOZ_WIDGET_GONK
+#include <android/log.h>
+#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, "MediaEncoder", ## args);
+#else
+#define LOG(args, ...)
+#endif
+
+namespace mozilla {
+
+OggWriter::OggWriter() : ContainerWriter()
+{
+  if (NS_FAILED(Init())) {
+    LOG("ERROR! Fail to initialize the OggWriter.");
+  }
+}
+
+nsresult
+OggWriter::Init()
+{
+  MOZ_ASSERT(!mInitialized);
+
+  // The serial number (serialno) should be a random number, for the current
+  // implementation where the output file contains only a single stream, this
+  // serialno is used to differentiate between files.
+  srand(static_cast<unsigned>(PR_Now()));
+  int rc = ogg_stream_init(&mOggStreamState, rand());
+
+  mPacket.b_o_s = 1;
+  mPacket.e_o_s = 0;
+  mPacket.granulepos = 0;
+  mPacket.packet = nullptr;
+  mPacket.packetno = 0;
+  mPacket.bytes = 0;
+
+  mInitialized = (rc == 0);
+
+  return (rc == 0) ? NS_OK : NS_ERROR_NOT_INITIALIZED;
+}
+
+nsresult
+OggWriter::WriteEncodedTrack(const nsTArray<uint8_t>& aBuffer, int aDuration,
+                             uint32_t aFlags)
+{
+  MOZ_ASSERT(!ogg_stream_eos(&mOggStreamState),
+             "No data can be written after eos has marked.");
+
+  // Set eos flag to true, and once the eos is written to a packet, there must
+  // not be anymore pages after a page has marked as eos.
+  if (aFlags & ContainerWriter::END_OF_STREAM) {
+    LOG("[OggWriter] Set e_o_s flag to true.");
+    mPacket.e_o_s = 1;
+  }
+
+  mPacket.packet = const_cast<uint8_t*>(aBuffer.Elements());
+  mPacket.bytes = aBuffer.Length();
+  mPacket.granulepos += aDuration;
+
+  // 0 returned on success. -1 returned in the event of internal error.
+  // The data in the packet is copied into the internal storage managed by the
+  // mOggStreamState, so we are free to alter the contents of mPacket after
+  // this call has returned.
+  int rc = ogg_stream_packetin(&mOggStreamState, &mPacket);
+  if (rc < 0) {
+    LOG("[OggWriter] Failed in ogg_stream_packetin! (%d).", rc);
+    return NS_ERROR_FAILURE;
+  }
+
+  if (mPacket.b_o_s) {
+    mPacket.b_o_s = 0;
+  }
+  mPacket.packetno++;
+  mPacket.packet = nullptr;
+
+  return NS_OK;
+}
+
+nsresult
+OggWriter::GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
+                            uint32_t aFlags)
+{
+  int rc = -1;
+  // Force generate a page even if the amount of packet data is not enough.
+  // Usually do so after a header packet.
+  if (aFlags & ContainerWriter::FLUSH_NEEDED) {
+    // rc = 0 means no packet to put into a page, or an internal error.
+    rc = ogg_stream_flush(&mOggStreamState, &mOggPage);
+  } else {
+    // rc = 0 means insufficient data has accumulated to fill a page, or an
+    // internal error has occurred.
+    rc = ogg_stream_pageout(&mOggStreamState, &mOggPage);
+  }
+
+  if (rc) {
+    aOutputBufs->AppendElement();
+    aOutputBufs->LastElement().SetLength(mOggPage.header_len +
+                                         mOggPage.body_len);
+    memcpy(aOutputBufs->LastElement().Elements(), mOggPage.header,
+           mOggPage.header_len);
+    memcpy(aOutputBufs->LastElement().Elements() + mOggPage.header_len,
+           mOggPage.body, mOggPage.body_len);
+  }
+
+  return (rc > 0) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+}
new file mode 100644
--- /dev/null
+++ b/content/media/ogg/OggWriter.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; 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/. */
+
+#ifndef OggWriter_h_
+#define OggWriter_h_
+
+#include "ContainerWriter.h"
+#include <ogg/ogg.h>
+
+namespace mozilla {
+/**
+ * WriteEncodedTrack inserts raw packets into Ogg stream (ogg_stream_state), and
+ * GetContainerData outputs an ogg_page when enough packets have been written
+ * to the Ogg stream.
+ * For more details, please reference:
+ * http://www.xiph.org/ogg/doc/libogg/encoding.html
+ */
+class OggWriter : public ContainerWriter
+{
+public:
+  OggWriter();
+
+  nsresult WriteEncodedTrack(const nsTArray<uint8_t>& aBuffer, int aDuration,
+                             uint32_t aFlags = 0) MOZ_OVERRIDE;
+
+  nsresult GetContainerData(nsTArray<nsTArray<uint8_t> >* aOutputBufs,
+                            uint32_t aFlags = 0) MOZ_OVERRIDE;
+
+private:
+  nsresult Init();
+
+  ogg_stream_state mOggStreamState;
+  ogg_page mOggPage;
+  ogg_packet mPacket;
+};
+}
+#endif
--- a/content/media/ogg/moz.build
+++ b/content/media/ogg/moz.build
@@ -5,16 +5,18 @@
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 MODULE = 'content'
 
 EXPORTS += [
     'OggCodecState.h',
     'OggDecoder.h',
     'OggReader.h',
+    'OggWriter.h',
 ]
 
 CPP_SOURCES += [
     'OggCodecState.cpp',
     'OggDecoder.cpp',
     'OggReader.cpp',
+    'OggWriter.cpp',
 ]
 
--- a/dom/bluetooth/BluetoothHfpManager.cpp
+++ b/dom/bluetooth/BluetoothHfpManager.cpp
@@ -1021,25 +1021,32 @@ respond_with_ok:
 
 void
 BluetoothHfpManager::Connect(const nsAString& aDeviceAddress,
                              const bool aIsHandsfree,
                              BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  NS_ENSURE_FALSE_VOID(gInShutdown);
-  NS_ENSURE_FALSE_VOID(mSocket);
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || gInShutdown) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    return;
+  }
+
+  if (mSocket) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT));
+    return;
+  }
 
   mNeedsUpdatingSdpRecords = true;
   mIsHandsfree = aIsHandsfree;
 
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE_VOID(bs);
-
   nsString uuid;
   if (aIsHandsfree) {
     BluetoothUuidHelper::GetString(BluetoothServiceClass::HANDSFREE, uuid);
   } else {
     BluetoothUuidHelper::GetString(BluetoothServiceClass::HEADSET, uuid);
   }
 
   if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
--- a/dom/bluetooth/BluetoothOppManager.cpp
+++ b/dom/bluetooth/BluetoothOppManager.cpp
@@ -252,20 +252,28 @@ BluetoothOppManager::Get()
 }
 
 void
 BluetoothOppManager::Connect(const nsAString& aDeviceAddress,
                              BluetoothReplyRunnable* aRunnable)
 {
   MOZ_ASSERT(NS_IsMainThread());
 
-  NS_ENSURE_FALSE_VOID(mSocket);
+  BluetoothService* bs = BluetoothService::Get();
+  if (!bs || sInShutdown) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_NO_AVAILABLE_RESOURCE));
+    return;
+  }
 
-  BluetoothService* bs = BluetoothService::Get();
-  NS_ENSURE_TRUE_VOID(bs);
+  if (mSocket) {
+    DispatchBluetoothReply(aRunnable, BluetoothValue(),
+                           NS_LITERAL_STRING(ERR_REACHED_CONNECTION_LIMIT));
+    return;
+  }
 
   mNeedsUpdatingSdpRecords = true;
 
   nsString uuid;
   BluetoothUuidHelper::GetString(BluetoothServiceClass::OBJECT_PUSH, uuid);
 
   if (NS_FAILED(bs->GetServiceChannel(aDeviceAddress, uuid, this))) {
     DispatchBluetoothReply(aRunnable, BluetoothValue(),
--- a/dom/bluetooth/BluetoothProfileManagerBase.h
+++ b/dom/bluetooth/BluetoothProfileManagerBase.h
@@ -3,16 +3,18 @@
 /* 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_bluetoothprofilemanagerbase_h__
 #define mozilla_dom_bluetooth_bluetoothprofilemanagerbase_h__
 
 #define ERR_SERVICE_CHANNEL_NOT_FOUND "DeviceChannelRetrievalError"
+#define ERR_REACHED_CONNECTION_LIMIT "ReachedConnectionLimitError"
+#define ERR_NO_AVAILABLE_RESOURCE "NoAvailableResourceError"
 
 #include "BluetoothCommon.h"
 
 BEGIN_BLUETOOTH_NAMESPACE
 
 class BluetoothProfileManagerBase : public nsISupports
 {
 public:
--- a/dom/bluetooth/linux/BluetoothDBusService.cpp
+++ b/dom/bluetooth/linux/BluetoothDBusService.cpp
@@ -1561,18 +1561,23 @@ EventFilter(DBusConnection* aConn, DBusM
   }
 
   if (!errorStr.IsEmpty()) {
     NS_WARNING(NS_ConvertUTF16toUTF8(errorStr).get());
     return DBUS_HANDLER_RESULT_NOT_YET_HANDLED;
   }
 
   BluetoothSignal signal(signalName, signalPath, v);
-  nsRefPtr<DistributeBluetoothSignalTask> task
-    = new DistributeBluetoothSignalTask(signal);
+  nsRefPtr<nsRunnable> task;
+  if (signalInterface.EqualsLiteral(DBUS_SINK_IFACE)) {
+    task = new SinkPropertyChangedHandler(signal);
+  } else {
+    task = new DistributeBluetoothSignalTask(signal);
+  }
+
   NS_DispatchToMainThread(task);
 
   return DBUS_HANDLER_RESULT_HANDLED;
 }
 
 static bool
 GetDefaultAdapterPath(BluetoothValue& aValue, nsString& aError)
 {
--- a/dom/system/gonk/NetworkManager.js
+++ b/dom/system/gonk/NetworkManager.js
@@ -68,46 +68,28 @@ const WIFI_FIRMWARE_STATION       = "STA
 const WIFI_SECURITY_TYPE_NONE     = "open";
 const WIFI_SECURITY_TYPE_WPA_PSK  = "wpa-psk";
 const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
 const WIFI_CTRL_INTERFACE         = "wl0.1";
 
 const NETWORK_INTERFACE_UP   = "up";
 const NETWORK_INTERFACE_DOWN = "down";
 
-// Settings DB path for Wifi tethering.
-const SETTINGS_WIFI_ENABLED            = "tethering.wifi.enabled";
-const SETTINGS_WIFI_SSID               = "tethering.wifi.ssid";
-const SETTINGS_WIFI_SECURITY_TYPE      = "tethering.wifi.security.type";
-const SETTINGS_WIFI_SECURITY_PASSWORD  = "tethering.wifi.security.password";
-const SETTINGS_WIFI_IP                 = "tethering.wifi.ip";
-const SETTINGS_WIFI_PREFIX             = "tethering.wifi.prefix";
-const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
-const SETTINGS_WIFI_DHCPSERVER_ENDIP   = "tethering.wifi.dhcpserver.endip";
-const SETTINGS_WIFI_DNS1               = "tethering.wifi.dns1";
-const SETTINGS_WIFI_DNS2               = "tethering.wifi.dns2";
+const TETHERING_STATE_ONGOING = "ongoing";
+const TETHERING_STATE_IDLE    = "idle";
 
 // Settings DB path for USB tethering.
 const SETTINGS_USB_ENABLED             = "tethering.usb.enabled";
 const SETTINGS_USB_IP                  = "tethering.usb.ip";
 const SETTINGS_USB_PREFIX              = "tethering.usb.prefix";
 const SETTINGS_USB_DHCPSERVER_STARTIP  = "tethering.usb.dhcpserver.startip";
 const SETTINGS_USB_DHCPSERVER_ENDIP    = "tethering.usb.dhcpserver.endip";
 const SETTINGS_USB_DNS1                = "tethering.usb.dns1";
 const SETTINGS_USB_DNS2                = "tethering.usb.dns2";
 
-// Default value for WIFI tethering.
-const DEFAULT_WIFI_IP                  = "192.168.1.1";
-const DEFAULT_WIFI_PREFIX              = "24";
-const DEFAULT_WIFI_DHCPSERVER_STARTIP  = "192.168.1.10";
-const DEFAULT_WIFI_DHCPSERVER_ENDIP    = "192.168.1.30";
-const DEFAULT_WIFI_SSID                = "FirefoxHotspot";
-const DEFAULT_WIFI_SECURITY_TYPE       = "open";
-const DEFAULT_WIFI_SECURITY_PASSWORD   = "1234567890";
-
 // Default value for USB tethering.
 const DEFAULT_USB_IP                   = "192.168.0.1";
 const DEFAULT_USB_PREFIX               = "24";
 const DEFAULT_USB_DHCPSERVER_STARTIP   = "192.168.0.10";
 const DEFAULT_USB_DHCPSERVER_ENDIP     = "192.168.0.30";
 
 const DEFAULT_DNS1                     = "8.8.8.8";
 const DEFAULT_DNS2                     = "8.8.4.4";
@@ -178,54 +160,35 @@ function NetworkManager() {
   this._tetheringInterface[TETHERING_TYPE_USB] = {externalInterface: DEFAULT_3G_INTERFACE_NAME,
                                                   internalInterface: DEFAULT_USB_INTERFACE_NAME};
   this._tetheringInterface[TETHERING_TYPE_WIFI] = {externalInterface: DEFAULT_3G_INTERFACE_NAME,
                                                    internalInterface: DEFAULT_WIFI_INTERFACE_NAME};
 
   this.initTetheringSettings();
 
   let settingsLock = gSettingsService.createLock();
-  // Read wifi tethering data from settings DB.
-  settingsLock.get(SETTINGS_WIFI_SSID, this);
-  settingsLock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
-  settingsLock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
-  settingsLock.get(SETTINGS_WIFI_IP, this);
-  settingsLock.get(SETTINGS_WIFI_PREFIX, this);
-  settingsLock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
-  settingsLock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
-  settingsLock.get(SETTINGS_WIFI_DNS1, this);
-  settingsLock.get(SETTINGS_WIFI_DNS2, this);
   // Read usb tethering data from settings DB.
   settingsLock.get(SETTINGS_USB_IP, this);
   settingsLock.get(SETTINGS_USB_PREFIX, this);
   settingsLock.get(SETTINGS_USB_DHCPSERVER_STARTIP, this);
   settingsLock.get(SETTINGS_USB_DHCPSERVER_ENDIP, this);
   settingsLock.get(SETTINGS_USB_DNS1, this);
   settingsLock.get(SETTINGS_USB_DNS2, this);
-
-  this.setAndConfigureActive();
+  settingsLock.get(SETTINGS_USB_ENABLED, this);
 
-  let self = this;
-  this.waitForConnectionReadyCallback = null;
-  settingsLock.get(SETTINGS_WIFI_ENABLED, {
-    handle: function (aName, aResult) {
-      if (!aResult) {
-        return;
-      }
-      // Turn on wifi tethering when the mobile data connection is established.
-      self.waitForConnectionReadyCallback = (function callback() {
-        let settingsLock = gSettingsService.createLock();
-        settingsLock.set(SETTINGS_WIFI_ENABLED, aResult, null);
-      });
-    },
+  this._usbTetheringSettingsToRead = [SETTINGS_USB_IP,
+                                      SETTINGS_USB_PREFIX,
+                                      SETTINGS_USB_DHCPSERVER_STARTIP,
+                                      SETTINGS_USB_DHCPSERVER_ENDIP,
+                                      SETTINGS_USB_DNS1,
+                                      SETTINGS_USB_DNS2,
+                                      SETTINGS_USB_ENABLED];
 
-    handleError: function (aErrorMessage) {
-      debug("Error reading the 'tethering.wifi.enabled' setting: " + aErrorMessage);
-    }
-  });
+  this.wantConnectionEvent = null;
+  this.setAndConfigureActive();
 
   ppmm.addMessageListener('NetworkInterfaceList:ListInterface', this);
 
   // Used in resolveHostname().
   defineLazyRegExp(this, "REGEXP_IPV4", "^\\d{1,3}(?:\\.\\d{1,3}){3}$");
   defineLazyRegExp(this, "REGEXP_IPV6", "^[\\da-fA-F]{4}(?::[\\da-fA-F]{4}){7}$");
 }
 NetworkManager.prototype = {
@@ -262,21 +225,19 @@ NetworkManager.prototype = {
             // Remove pre-created default route and let setAndConfigureActive()
             // to set default route only on preferred network
             this.removeDefaultRoute(network.name);
             this.setAndConfigureActive();
             // Update data connection when Wifi connected/disconnected
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_WIFI) {
               this.mRIL.updateRILNetworkInterface();
             }
-            // Turn on wifi tethering when the callback is set.
-            if (this.waitForConnectionReadyCallback) {
-              this.waitForConnectionReadyCallback.call(this);
-              this.waitForConnectionReadyCallback = null;
-            }
+
+            this.onConnectionChanged(network);
+
             // Probing the public network accessibility after routing table is ready
             CaptivePortalDetectionHelper.notify(CaptivePortalDetectionHelper.EVENT_CONNECT, this.active);
             break;
           case Ci.nsINetworkInterface.NETWORK_STATE_DISCONNECTED:
             // Remove host route for data calls
             if (network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE ||
                 network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_MMS ||
                 network.type == Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE_SUPL) {
@@ -699,91 +660,110 @@ NetworkManager.prototype = {
     }
   },
 
   // nsISettingsServiceCallback
 
   tetheringSettings: {},
 
   initTetheringSettings: function initTetheringSettings() {
-    this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
     this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
-    this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
-    this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
-    this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
-    this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
-    this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
-    this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
-    this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
-    this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
-    this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
     this.tetheringSettings[SETTINGS_USB_IP] = DEFAULT_USB_IP;
     this.tetheringSettings[SETTINGS_USB_PREFIX] = DEFAULT_USB_PREFIX;
     this.tetheringSettings[SETTINGS_USB_DHCPSERVER_STARTIP] = DEFAULT_USB_DHCPSERVER_STARTIP;
     this.tetheringSettings[SETTINGS_USB_DHCPSERVER_ENDIP] = DEFAULT_USB_DHCPSERVER_ENDIP;
     this.tetheringSettings[SETTINGS_USB_DNS1] = DEFAULT_DNS1;
     this.tetheringSettings[SETTINGS_USB_DNS2] = DEFAULT_DNS2;
   },
 
+  _requestCount: 0,
+
   handle: function handle(aName, aResult) {
     switch(aName) {
       case SETTINGS_USB_ENABLED:
-        this.handleUSBTetheringToggle(aResult);
-        break;
-      // SETTINGS_WIFI_ENABLED is handled in WifiManager.js to deal with
-      // the interaction between wifi and wifi tethering settings. Also, we
-      // update tetheringSettings[SETTINGS_WIFI_ENABLED] in setWifiTethering
-      // function.
-      case SETTINGS_WIFI_SSID:
-      case SETTINGS_WIFI_SECURITY_TYPE:
-      case SETTINGS_WIFI_SECURITY_PASSWORD:
-      case SETTINGS_WIFI_IP:
-      case SETTINGS_WIFI_PREFIX:
-      case SETTINGS_WIFI_DHCPSERVER_STARTIP:
-      case SETTINGS_WIFI_DHCPSERVER_ENDIP:
-      case SETTINGS_WIFI_DNS1:
-      case SETTINGS_WIFI_DNS2:
+        this._oldUsbTetheringEnabledState = this.tetheringSettings[SETTINGS_USB_ENABLED];
       case SETTINGS_USB_IP:
       case SETTINGS_USB_PREFIX:
       case SETTINGS_USB_DHCPSERVER_STARTIP:
       case SETTINGS_USB_DHCPSERVER_ENDIP:
       case SETTINGS_USB_DNS1:
       case SETTINGS_USB_DNS2:
-        if (aResult) {
+        if (aResult !== null) {
           this.tetheringSettings[aName] = aResult;
         }
         debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
+        let index = this._usbTetheringSettingsToRead.indexOf(aName);
+
+        if (index != -1) {
+          this._usbTetheringSettingsToRead.splice(index, 1);
+        }
+
+        if (this._usbTetheringSettingsToRead.length) {
+          debug("We haven't read completely the usb Tethering data from settings db.");
+          break;
+        }
+
+        if (this._oldUsbTetheringEnabledState === this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+          debug("No changes for SETTINGS_USB_ENABLED flag. Nothing to do.");
+          break;
+        }
+
+        this._requestCount++;
+        if (this._requestCount === 1) {
+          this.handleUSBTetheringToggle(aResult);
+        }
         break;
     };
   },
 
   handleError: function handleError(aErrorMessage) {
     debug("There was an error while reading Tethering settings.");
     this.tetheringSettings = {};
-    this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
     this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
   },
 
   getNetworkInterface: function getNetworkInterface(type) {
     for each (let network in this.networkInterfaces) {
       if (network.type == type) {
         return network;
       }
     }
     return null;
   },
 
+  _usbTetheringAction: TETHERING_STATE_IDLE,
+
+  _usbTetheringSettingsToRead: [],
+
+  _oldUsbTetheringEnabledState: null,
+
   // External and internal interface name.
   _tetheringInterface: null,
 
-  handleUSBTetheringToggle: function handleUSBTetheringToggle(enable) {
-    if (this.tetheringSettings[SETTINGS_USB_ENABLED] == enable) {
+  handleLastRequest: function handleLastRequest() {
+    let count = this._requestCount;
+    this._requestCount = 0;
+
+    if (count === 1) {
+      if (this.wantConnectionEvent) {
+        if (this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+          this.wantConnectionEvent.call(this);
+        }
+        this.wantConnectionEvent = null;
+      }
       return;
     }
 
+    if (count > 1) {
+      this.handleUSBTetheringToggle(this.tetheringSettings[SETTINGS_USB_ENABLED]);
+      this.wantConnectionEvent = null;
+    }
+  },
+
+  handleUSBTetheringToggle: function handleUSBTetheringToggle(enable) {
     if (!enable) {
       this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
       this.enableUsbRndis(false, this.enableUsbRndisResult);
       return;
     }
 
     if (this.active) {
       this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = this.active.name
@@ -792,82 +772,16 @@ NetworkManager.prototype = {
       if (mobile) {
         this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = mobile.name;
       }
     }
     this.tetheringSettings[SETTINGS_USB_ENABLED] = true;
     this.enableUsbRndis(true, this.enableUsbRndisResult);
   },
 
-  getWifiTetheringParameters: function getWifiTetheringParameters(enable, tetheringinterface) {
-    let ssid;
-    let securityType;
-    let securityId;
-    let interfaceIp;
-    let prefix;
-    let dhcpStartIp;
-    let dhcpEndIp;
-    let dns1;
-    let dns2;
-    let internalInterface = tetheringinterface.internalInterface;
-    let externalInterface = tetheringinterface.externalInterface;
-
-    ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
-    securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
-    securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
-    interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
-    prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
-    dhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
-    dhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
-    dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
-    dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
-
-    // Check the format to prevent netd from crash.
-    if (!ssid || ssid == "") {
-      debug("Invalid SSID value.");
-      return null;
-    }
-    if (securityType != WIFI_SECURITY_TYPE_NONE &&
-        securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
-        securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
-
-      debug("Invalid security type.");
-      return null;
-    }
-    if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
-      debug("Invalid security password.");
-      return null;
-    }
-    // Using the default values here until application supports these settings.
-    if (interfaceIp == "" || prefix == "" ||
-        dhcpStartIp == "" || dhcpEndIp == "") {
-      debug("Invalid subnet information.");
-      return null;
-    }
-
-    return {
-      ifname: internalInterface,
-      wifictrlinterfacename: WIFI_CTRL_INTERFACE,
-      ssid: ssid,
-      security: securityType,
-      key: securityId,
-      ip: interfaceIp,
-      prefix: prefix,
-      startIp: dhcpStartIp,
-      endIp: dhcpEndIp,
-      dns1: dns1,
-      dns2: dns2,
-      internalIfname: internalInterface,
-      externalIfname: externalInterface,
-      enable: enable,
-      mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
-      link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
-    };
-  },
-
   getUSBTetheringParameters: function getUSBTetheringParameters(enable, tetheringinterface) {
     let interfaceIp;
     let prefix;
     let dhcpStartIp;
     let dhcpEndIp;
     let dns1;
     let dns2;
     let internalInterface = tetheringinterface.internalInterface;
@@ -897,102 +811,95 @@ NetworkManager.prototype = {
       dns2: dns2,
       internalIfname: internalInterface,
       externalIfname: externalInterface,
       enable: enable,
       link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
     };
   },
 
-  get wifiTetheringEnabled() {
-    return this.tetheringSettings[SETTINGS_WIFI_ENABLED];
-  },
-
   notifyError: function notifyError(resetSettings, callback, msg) {
     if (resetSettings) {
       let settingsLock = gSettingsService.createLock();
-      this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
       // Disable wifi tethering with a useful error message for the user.
       settingsLock.set("tethering.wifi.enabled", false, null, msg);
     }
 
     debug("setWifiTethering: " + (msg ? msg : "success"));
 
     if (callback) {
       callback.wifiTetheringEnabledChange(msg);
     }
   },
 
   // Enable/disable WiFi tethering by sending commands to netd.
-  setWifiTethering: function setWifiTethering(enable, network, callback) {
-    if (this.tetheringSettings[SETTINGS_WIFI_ENABLED] == enable) {
-      this.notifyError(false, callback, "no change");
-      return;
-    }
-
+  setWifiTethering: function setWifiTethering(enable, network, config, callback) {
     if (!network) {
       this.notifyError(true, callback, "invalid network information");
       return;
     }
+
+    if (!config) {
+      this.notifyError(true, callback, "invalid configuration");
+      return;
+    }
+
     this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface = network.name;
 
     let mobile = this.getNetworkInterface(Ci.nsINetworkInterface.NETWORK_TYPE_MOBILE);
     // Update the real interface name
     if (mobile) {
       this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface = mobile.name;
     }
-    // Clear this flag to prevent unexpected action.
-    this.waitForConnectionReadyCallback = null;
 
-    let params = this.getWifiTetheringParameters(enable, this._tetheringInterface[TETHERING_TYPE_WIFI]);
-    if (!params) {
-      this.notifyError(true, callback, "invalid parameters");
-      return;
-    }
+    config.ifname         = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
+    config.internalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].internalInterface;
+    config.externalIfname = this._tetheringInterface[TETHERING_TYPE_WIFI].externalInterface;
+    config.wifictrlinterfacename = WIFI_CTRL_INTERFACE;
 
-    params.cmd = "setWifiTethering";
+    config.cmd = "setWifiTethering";
     // The callback function in controlMessage may not be fired immediately.
-    params.isAsync = true;
-    this.controlMessage(params, function setWifiTetheringResult(data) {
+    config.isAsync = true;
+    this.controlMessage(config, function setWifiTetheringResult(data) {
       let code = data.resultCode;
       let reason = data.resultReason;
       let enable = data.enable;
       let enableString = enable ? "Enable" : "Disable";
 
       debug(enableString + " Wifi tethering result: Code " + code + " reason " + reason);
-      // Update settings status.
-      this.tetheringSettings[SETTINGS_WIFI_ENABLED] = enable;
 
       if (isError(code)) {
         this.notifyError(true, callback, "netd command error");
       } else {
         this.notifyError(false, callback, null);
       }
     }.bind(this));
   },
 
   // Enable/disable USB tethering by sending commands to netd.
-  setUSBTethering: function setUSBTethering(enable, tetheringInterface) {
+  setUSBTethering: function setUSBTethering(enable,
+                                            tetheringInterface,
+                                            callback) {
     let params = this.getUSBTetheringParameters(enable, tetheringInterface);
 
     if (params === null) {
       params = {
         enable: enable,
         resultCode: NETD_COMMAND_ERROR,
         resultReason: "Invalid parameters"
       };
+      this.enableUsbRndis(false, null);
       this.usbTetheringResultReport(params);
-      this.enableUsbRndis(false, null);
       return;
     }
 
     params.cmd = "setUSBTethering";
     // The callback function in controlMessage may not be fired immediately.
     params.isAsync = true;
-    this.controlMessage(params, this.usbTetheringResultReport);
+    this.controlMessage(params, callback);
   },
 
   getUsbInterface: function getUsbInterface() {
     // Find the rndis interface.
     for (let i = 0; i < this.possibleInterface.length; i++) {
       try {
         let file = new FileUtils.File(KERNEL_NETWORK_ENTRY + "/" +
                                       this.possibleInterface[i]);
@@ -1007,17 +914,19 @@ NetworkManager.prototype = {
     return DEFAULT_USB_INTERFACE_NAME;
   },
 
   enableUsbRndisResult: function enableUsbRndisResult(data) {
     let result = data.result;
     let enable = data.enable;
     if (result) {
       this._tetheringInterface[TETHERING_TYPE_USB].internalInterface = this.getUsbInterface();
-      this.setUSBTethering(enable, this._tetheringInterface[TETHERING_TYPE_USB]);
+      this.setUSBTethering(enable,
+                           this._tetheringInterface[TETHERING_TYPE_USB],
+                           this.usbTetheringResultReport);
     } else {
       let params = {
         enable: false,
         resultCode: NETD_COMMAND_ERROR,
         resultReason: "Failed to set usb function"
       };
       this.usbTetheringResultReport(params);
       throw new Error("failed to set USB Function to adb");
@@ -1035,32 +944,106 @@ NetworkManager.prototype = {
     if (callback) {
       params.report = true;
     } else {
       params.report = false;
     }
 
     // The callback function in controlMessage may not be fired immediately.
     params.isAsync = true;
+    this._usbTetheringAction = TETHERING_STATE_ONGOING;
     this.controlMessage(params, callback);
   },
 
   usbTetheringResultReport: function usbTetheringResultReport(data) {
     let code = data.resultCode;
     let reason = data.resultReason;
     let enable = data.enable;
     let enableString = enable ? "Enable" : "Disable";
     let settingsLock = gSettingsService.createLock();
 
     debug(enableString + " USB tethering result: Code " + code + " reason " + reason);
+    this._usbTetheringAction = TETHERING_STATE_IDLE;
     // Disable tethering settings when fail to enable it.
     if (isError(code)) {
       this.tetheringSettings[SETTINGS_USB_ENABLED] = false;
       settingsLock.set("tethering.usb.enabled", false, null);
+      // Skip others request when we found an error.
+      this._requestCount = 0;
+    } else {
+      this.handleLastRequest();
     }
+
+  },
+
+  updateUpStream: function updateUpStream(previous, current, callback) {
+    let params = {
+      cmd: "updateUpStream",
+      isAsync: true,
+      previous: previous,
+      current: current
+    };
+
+    this.controlMessage(params, callback);
+  },
+
+  onConnectionChangedReport: function onConnectionChangedReport(data) {
+    let code = data.resultCode;
+    let reason = data.resultReason;
+
+    debug("onConnectionChangedReport result: Code " + code + " reason " + reason);
+
+    if (!isError(code)) {
+      // Update the external interface.
+      this._tetheringInterface[TETHERING_TYPE_USB].externalInterface = data.current.externalIfname;
+      debug("Change the interface name to " + data.current.externalIfname);
+    }
+  },
+
+  onConnectionChanged: function onConnectionChanged(network) {
+    if (network.state != Ci.nsINetworkInterface.NETWORK_STATE_CONNECTED) {
+      debug("We are only interested in CONNECTED event");
+      return;
+    }
+
+    if (!this.tetheringSettings[SETTINGS_USB_ENABLED]) {
+      debug("Usb tethering settings is not enabled");
+      return;
+    }
+
+    if (this._tetheringInterface[TETHERING_TYPE_USB].externalInterface ===
+        this.active.name) {
+      debug("The active interface is the same");
+      return;
+    }
+
+    let previous = {
+      internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
+      externalIfname: this._tetheringInterface[TETHERING_TYPE_USB].externalInterface
+    };
+
+    let current = {
+      internalIfname: this._tetheringInterface[TETHERING_TYPE_USB].internalInterface,
+      externalIfname: network.name
+    };
+
+    let callback = (function () {
+      // Update external network interface.
+      debug("Update upstream interface to " + network.name);
+      this.updateUpStream(previous, current, this.onConnectionChangedReport);
+    }).bind(this);
+
+    if (this._usbTetheringAction === TETHERING_STATE_ONGOING) {
+      debug("Postpone the event and handle it when state is idle.");
+      this.wantConnectionEvent = callback;
+      return;
+    }
+    this.wantConnectionEvent = null;
+
+    callback.call(this);
   }
 };
 
 let CaptivePortalDetectionHelper = (function() {
 
   const EVENT_CONNECT = "Connect";
   const EVENT_DISCONNECT = "Disconnect";
   let _ongoingInterface = null;
--- a/dom/system/gonk/net_worker.js
+++ b/dom/system/gonk/net_worker.js
@@ -111,19 +111,16 @@ let gUSBFailChain = [stopSoftAP,
                      setIpForwardingEnabled,
                      stopTethering];
 
 function usbTetheringFail(params) {
   // Notify the main thread.
   postMessage(params);
   // Try to roll back to ensure
   // we don't leave the network systems in limbo.
-  let functionChain = [setIpForwardingEnabled,
-                       stopTethering];
-
   // This parameter is used to disable ipforwarding.
   params.enable = false;
   chain(params, gUSBFailChain, null);
 
   // Disable usb rndis function.
   enableUsbRndis({enable: false, report: false});
 }
 
@@ -142,16 +139,28 @@ function networkInterfaceStatsFail(param
 function networkInterfaceStatsSuccess(params) {
   // Notify the main thread.
   params.txBytes = parseFloat(params.resultReason);
 
   postMessage(params);
   return true;
 }
 
+function updateUpStreamSuccess(params) {
+  // Notify the main thread.
+  postMessage(params);
+  return true;
+}
+
+function updateUpStreamFail(params) {
+  // Notify the main thread.
+  postMessage(params);
+  return true;
+}
+
 /**
  * Get network interface properties from the system property table.
  *
  * @param ifname
  *        Name of the network interface.
  */
 function getIFProperties(ifname) {
   return {
@@ -437,16 +446,28 @@ function setAccessPoint(params, callback
                 " " + params.wifictrlinterfacename +
                 " \"" + escapeQuote(params.ssid) + "\"" +
                 " " + params.security +
                 " \"" + escapeQuote(params.key) + "\"" +
                 " " + "6 0 8";
   return doCommand(command, callback);
 }
 
+function cleanUpStream(params, callback) {
+  let command = "nat disable " + params.previous.internalIfname + " " +
+                params.previous.externalIfname + " " + "0";
+  return doCommand(command, callback);
+}
+
+function createUpStream(params, callback) {
+  let command = "nat enable " + params.current.internalIfname + " " +
+                params.current.externalIfname + " " + "0";
+  return doCommand(command, callback);
+}
+
 /**
  * Modify usb function's property to turn on USB RNDIS function
  */
 function enableUsbRndis(params) {
   let report = params.report;
   let retry = 0;
 
   // For some reason, rndis doesn't play well with diag,modem,nmea.
@@ -611,16 +632,26 @@ function setWifiTethering(params) {
     // Disable Wifi tethering.
     debug("Stopping Wifi Tethering on " +
            params.internalIfname + "<->" + params.externalIfname);
     chain(params, gWifiDisableChain, wifiTetheringFail);
   }
   return true;
 }
 
+let gUpdateUpStreamChain = [cleanUpStream,
+                            createUpStream,
+                            updateUpStreamSuccess];
+/**
+ * handling upstream interface change event.
+ */
+function updateUpStream(params) {
+  chain(params, gUpdateUpStreamChain, updateUpStreamFail);
+}
+
 let gUSBEnableChain = [setInterfaceUp,
                        enableNat,
                        setIpForwardingEnabled,
                        tetherInterface,
                        startTethering,
                        setDnsForwarders,
                        usbTetheringSuccess];
 
--- a/dom/system/gonk/nsINetworkManager.idl
+++ b/dom/system/gonk/nsINetworkManager.idl
@@ -107,17 +107,17 @@ interface nsINetworkStatsCallback : nsIS
                              in unsigned long rxBytes,
                              in unsigned long txBytes,
                              in jsval date);
 };
 
 /**
  * Manage network interfaces.
  */
-[scriptable, uuid(4bee9633-47ed-47ae-b92b-3e0679087561)]
+[scriptable, uuid(24f8ede0-c862-11e2-8b8b-0800200c9a66)]
 interface nsINetworkManager : nsISupports
 {
   /**
    * Register the given network interface with the network manager.
    *
    * Consumers will be notified with the 'network-interface-registered'
    * observer notification.
    *
@@ -176,32 +176,30 @@ interface nsINetworkManager : nsISupport
    *
    * @param network
    *        Network to route all network traffic to. If this is null,
    *        a previous override is canceled.
    */
   long overrideActive(in nsINetworkInterface network);
 
   /**
-   * Returns whether or not wifi tethering is currently enabled.
-   */
-  readonly attribute boolean wifiTetheringEnabled;
-
-  /**
    * Enable or disable Wifi Tethering
    *
    * @param enabled
    *        Boolean that indicates whether tethering should be enabled (true) or disabled (false).
    * @param network
    *        The Wifi network interface with at least name of network interface.
+   * @param config
+   *        The Wifi Tethering configuration from settings db.
    * @param callback
    *        Callback function used to report status to WifiManager.
    */
   void setWifiTethering(in boolean enabled,
                         in nsINetworkInterface networkInterface,
+                        in jsval config,
                         in nsIWifiTetheringCallback callback);
 
   /**
    * Retrieve network interface stats.
    *
    * @param networkType
    *        Select the Network interface to request estats.
    *
--- a/dom/wifi/WifiWorker.js
+++ b/dom/wifi/WifiWorker.js
@@ -19,16 +19,51 @@ const WIFIWORKER_CID        = Components
 const WIFIWORKER_WORKER     = "resource://gre/modules/wifi_worker.js";
 
 const kNetworkInterfaceStateChangedTopic = "network-interface-state-changed";
 const kMozSettingsChangedObserverTopic   = "mozsettings-changed";
 
 const MAX_RETRIES_ON_AUTHENTICATION_FAILURE = 2;
 const MAX_SUPPLICANT_LOOP_ITERATIONS = 4;
 
+// Settings DB path for wifi
+const SETTINGS_WIFI_ENABLED            = "wifi.enabled";
+const SETTINGS_WIFI_DEBUG_ENABLED      = "wifi.debugging.enabled";
+// Settings DB path for Wifi tethering.
+const SETTINGS_WIFI_TETHERING_ENABLED  = "tethering.wifi.enabled";
+const SETTINGS_WIFI_SSID               = "tethering.wifi.ssid";
+const SETTINGS_WIFI_SECURITY_TYPE      = "tethering.wifi.security.type";
+const SETTINGS_WIFI_SECURITY_PASSWORD  = "tethering.wifi.security.password";
+const SETTINGS_WIFI_IP                 = "tethering.wifi.ip";
+const SETTINGS_WIFI_PREFIX             = "tethering.wifi.prefix";
+const SETTINGS_WIFI_DHCPSERVER_STARTIP = "tethering.wifi.dhcpserver.startip";
+const SETTINGS_WIFI_DHCPSERVER_ENDIP   = "tethering.wifi.dhcpserver.endip";
+const SETTINGS_WIFI_DNS1               = "tethering.wifi.dns1";
+const SETTINGS_WIFI_DNS2               = "tethering.wifi.dns2";
+
+// Default value for WIFI tethering.
+const DEFAULT_WIFI_IP                  = "192.168.1.1";
+const DEFAULT_WIFI_PREFIX              = "24";
+const DEFAULT_WIFI_DHCPSERVER_STARTIP  = "192.168.1.10";
+const DEFAULT_WIFI_DHCPSERVER_ENDIP    = "192.168.1.30";
+const DEFAULT_WIFI_SSID                = "FirefoxHotspot";
+const DEFAULT_WIFI_SECURITY_TYPE       = "open";
+const DEFAULT_WIFI_SECURITY_PASSWORD   = "1234567890";
+const DEFAULT_DNS1                     = "8.8.8.8";
+const DEFAULT_DNS2                     = "8.8.4.4";
+
+const WIFI_FIRMWARE_AP            = "AP";
+const WIFI_FIRMWARE_STATION       = "STA";
+const WIFI_SECURITY_TYPE_NONE     = "open";
+const WIFI_SECURITY_TYPE_WPA_PSK  = "wpa-psk";
+const WIFI_SECURITY_TYPE_WPA2_PSK = "wpa2-psk";
+
+const NETWORK_INTERFACE_UP   = "up";
+const NETWORK_INTERFACE_DOWN = "down";
+
 XPCOMUtils.defineLazyServiceGetter(this, "gNetworkManager",
                                    "@mozilla.org/network/manager;1",
                                    "nsINetworkManager");
 
 XPCOMUtils.defineLazyServiceGetter(this, "gSettingsService",
                                    "@mozilla.org/settingsService;1",
                                    "nsISettingsService");
 
@@ -1137,17 +1172,17 @@ var WifiManager = (function() {
             });
           });
         });
       });
     }
   }
 
   // Get wifi interface and load wifi driver when enable Ap mode.
-  manager.setWifiApEnabled = function(enabled, callback) {
+  manager.setWifiApEnabled = function(enabled, configuration, callback) {
     if (enabled) {
       manager.tetheringState = "INITIALIZING";
       getProperty("wifi.interface", "tiwlan0", function (ifname) {
         if (!ifname) {
           callback();
           manager.tetheringState = "UNINITIALIZED";
           return;
         }
@@ -1157,17 +1192,18 @@ var WifiManager = (function() {
             callback();
             manager.tetheringState = "UNINITIALIZED";
             return;
           }
 
           function doStartWifiTethering() {
             cancelWaitForDriverReadyTimer();
             WifiNetworkInterface.name = manager.ifname;
-            gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface, function(result) {
+            gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
+                                             configuration, function(result) {
               if (result) {
                 manager.tetheringState = "UNINITIALIZED";
               } else {
                 manager.tetheringState = "COMPLETED";
               }
               // Pop out current request.
               callback();
               // Should we fire a dom event if we fail to set wifi tethering  ?
@@ -1177,17 +1213,18 @@ var WifiManager = (function() {
 
           // Driver startup on certain platforms takes longer than it takes
           // for us to return from loadDriver, so wait 2 seconds before
           // turning on Wifi tethering.
           createWaitForDriverReadyTimer(doStartWifiTethering);
         });
       });
     } else {
-      gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface, function(result) {
+      gNetworkManager.setWifiTethering(enabled, WifiNetworkInterface,
+                                       configuration, function(result) {
         // Should we fire a dom event if we fail to set wifi tethering  ?
         debug("Disable Wifi tethering result: " + (result ? result : "successfully"));
         // Unload wifi driver even if we fail to control wifi tethering.
         unloadDriver(function(status) {
           if (status < 0) {
             debug("Fail to unload wifi driver");
           }
           manager.tetheringState = "UNINITIALIZED";
@@ -1656,16 +1693,20 @@ function WifiWorker() {
 
   this.currentNetwork = null;
   this.ipAddress = "";
 
   this._lastConnectionInfo = null;
   this._connectionInfoTimer = null;
   this._reconnectOnDisconnect = false;
 
+  // Users of instances of nsITimer should keep a reference to the timer until 
+  // it is no longer needed in order to assure the timer is fired.
+  this._callbackTimer = null;
+
   // XXX On some phones (Otoro and Unagi) the wifi driver doesn't play nicely
   // with the automatic scans that wpa_supplicant does (it appears that the
   // driver forgets that it's returned scan results and then refuses to try to
   // rescan. In order to detect this case we start a timer when we enter the
   // SCANNING state and reset it whenever we either get scan results or leave
   // the SCANNING state. If the timer fires, we assume that we are stuck and
   // forceably try to unstick the supplican, also turning on background
   // scanning to avoid having to constantly poke the supplicant.
@@ -1907,17 +1948,17 @@ function WifiWorker() {
         // select a better network!
         if (self._needToEnableNetworks) {
           self._enableAllNetworks();
           self._needToEnableNetworks = false;
         }
 
         // We get the ASSOCIATED event when we've associated but not connected, so
         // wait until the handshake is complete.
-        if (this.fromStatus) {
+        if (this.fromStatus || !self.currentNetwork) {
           // In this case, we connected to an already-connected wpa_supplicant,
           // because of that we need to gather information about the current
           // network here.
           self.currentNetwork = { ssid: quote(WifiManager.connectionInfo.ssid),
                                   netId: WifiManager.connectionInfo.id };
           WifiManager.getNetworkConfiguration(self.currentNetwork, function(){});
         }
 
@@ -2109,47 +2150,72 @@ function WifiWorker() {
   };
 
   // Read the 'wifi.enabled' setting in order to start with a known
   // value at boot time. The handle() will be called after reading.
   //
   // nsISettingsServiceCallback implementation
   var initWifiEnabledCb = {
     handle: function handle(aName, aResult) {
-      if (aName !== "wifi.enabled")
+      if (aName !== SETTINGS_WIFI_ENABLED)
         return;
       if (aResult === null)
         aResult = true;
-      self.setWifiEnabled({enabled: aResult});
+      self.handleWifiEnabled(aResult);
     },
     handleError: function handleError(aErrorMessage) {
       debug("Error reading the 'wifi.enabled' setting. Default to wifi on.");
-      self.setWifiEnabled({enabled: true});
+      self.handleWifiEnabled(true);
     }
   };
 
   var initWifiDebuggingEnabledCb = {
     handle: function handle(aName, aResult) {
-      if (aName !== "wifi.debugging.enabled")
+      if (aName !== SETTINGS_WIFI_DEBUG_ENABLED)
         return;
       if (aResult === null)
         aResult = false;
       DEBUG = aResult;
       updateDebug();
     },
     handleError: function handleError(aErrorMessage) {
       debug("Error reading the 'wifi.debugging.enabled' setting. Default to debugging off.");
       DEBUG = false;
       updateDebug();
     }
   };
 
+  this.initTetheringSettings();
+
   let lock = gSettingsService.createLock();
-  lock.get("wifi.enabled", initWifiEnabledCb);
-  lock.get("wifi.debugging.enabled", initWifiDebuggingEnabledCb);
+  lock.get(SETTINGS_WIFI_ENABLED, initWifiEnabledCb);
+  lock.get(SETTINGS_WIFI_DEBUG_ENABLED, initWifiDebuggingEnabledCb);
+
+  lock.get(SETTINGS_WIFI_SSID, this);
+  lock.get(SETTINGS_WIFI_SECURITY_TYPE, this);
+  lock.get(SETTINGS_WIFI_SECURITY_PASSWORD, this);
+  lock.get(SETTINGS_WIFI_IP, this);
+  lock.get(SETTINGS_WIFI_PREFIX, this);
+  lock.get(SETTINGS_WIFI_DHCPSERVER_STARTIP, this);
+  lock.get(SETTINGS_WIFI_DHCPSERVER_ENDIP, this);
+  lock.get(SETTINGS_WIFI_DNS1, this);
+  lock.get(SETTINGS_WIFI_DNS2, this);
+  lock.get(SETTINGS_WIFI_TETHERING_ENABLED, this);
+
+  this._wifiTetheringSettingsToRead = [SETTINGS_WIFI_SSID,
+                                       SETTINGS_WIFI_SECURITY_TYPE,
+                                       SETTINGS_WIFI_SECURITY_PASSWORD,
+                                       SETTINGS_WIFI_IP,
+                                       SETTINGS_WIFI_PREFIX,
+                                       SETTINGS_WIFI_DHCPSERVER_STARTIP,
+                                       SETTINGS_WIFI_DHCPSERVER_ENDIP,
+                                       SETTINGS_WIFI_DNS1,
+                                       SETTINGS_WIFI_DNS2,
+                                       SETTINGS_WIFI_TETHERING_ENABLED];
+
 }
 
 function translateState(state) {
   switch (state) {
     case "INTERFACE_DISABLED":
     case "INACTIVE":
     case "SCANNING":
     case "DISCONNECTED":
@@ -2173,22 +2239,42 @@ WifiWorker.prototype = {
   classInfo: XPCOMUtils.generateCI({classID: WIFIWORKER_CID,
                                     contractID: WIFIWORKER_CONTRACTID,
                                     classDescription: "WifiWorker",
                                     interfaces: [Ci.nsIWorkerHolder,
                                                  Ci.nsIWifi,
                                                  Ci.nsIObserver]}),
 
   QueryInterface: XPCOMUtils.generateQI([Ci.nsIWorkerHolder,
-                                         Ci.nsIWifi]),
+                                         Ci.nsIWifi,
+                                         Ci.nsISettingsServiceCallback]),
 
   disconnectedByWifi: false,
 
   disconnectedByWifiTethering: false,
 
+  _wifiTetheringSettingsToRead: [],
+
+  _oldWifiTetheringEnabledState: null,
+
+  tetheringSettings: {},
+
+  initTetheringSettings: function initTetheringSettings() {
+    this.tetheringSettings[SETTINGS_WIFI_ENABLED] = false;
+    this.tetheringSettings[SETTINGS_WIFI_SSID] = DEFAULT_WIFI_SSID;
+    this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE] = DEFAULT_WIFI_SECURITY_TYPE;
+    this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD] = DEFAULT_WIFI_SECURITY_PASSWORD;
+    this.tetheringSettings[SETTINGS_WIFI_IP] = DEFAULT_WIFI_IP;
+    this.tetheringSettings[SETTINGS_WIFI_PREFIX] = DEFAULT_WIFI_PREFIX;
+    this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP] = DEFAULT_WIFI_DHCPSERVER_STARTIP;
+    this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP] = DEFAULT_WIFI_DHCPSERVER_ENDIP;
+    this.tetheringSettings[SETTINGS_WIFI_DNS1] = DEFAULT_DNS1;
+    this.tetheringSettings[SETTINGS_WIFI_DNS2] = DEFAULT_DNS2;
+  },
+
   // Internal methods.
   waitForScan: function(callback) {
     this.wantScanResults.push(callback);
   },
 
   // In order to select a specific network, we disable the rest of the
   // networks known to us. However, in general, we want the supplicant to
   // connect to which ever network it thinks is best, so when we select the
@@ -2580,22 +2666,21 @@ WifiWorker.prototype = {
           this._stateRequests.shift();
         }
         // Don't remove more than one request if the previous one failed.
       } while (success &&
                this._stateRequests.length &&
                !("callback" in this._stateRequests[0]) &&
                this._stateRequests[0].enabled === state);
     }
-
     // If there were requests queued after this one, run them.
     if (this._stateRequests.length > 0) {
-      let timer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
       let self = this;
-      timer.initWithCallback(function(timer) {
+      this._callbackTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+      this._callbackTimer.initWithCallback(function(timer) {
         if ("callback" in self._stateRequests[0]) {
           self._stateRequests[0].callback.call(self, self._stateRequests[0].enabled);
         } else {
           WifiManager.setWifiEnabled(self._stateRequests[0].enabled,
                                      self._setWifiEnabledCallback.bind(this));
         }
         timer = null;
       }, 1000, Ci.nsITimer.TYPE_ONE_SHOT);
@@ -2638,18 +2723,85 @@ WifiWorker.prototype = {
       }
     }
   },
 
   queueRequest: function(enabled, callback) {
     this.setWifiEnabled({enabled: enabled, callback: callback});
   },
 
+  getWifiTetheringParameters: function getWifiTetheringParameters(enable) {
+    let ssid;
+    let securityType;
+    let securityId;
+    let interfaceIp;
+    let prefix;
+    let dhcpStartIp;
+    let dhcpEndIp;
+    let dns1;
+    let dns2;
+
+    ssid = this.tetheringSettings[SETTINGS_WIFI_SSID];
+    securityType = this.tetheringSettings[SETTINGS_WIFI_SECURITY_TYPE];
+    securityId = this.tetheringSettings[SETTINGS_WIFI_SECURITY_PASSWORD];
+    interfaceIp = this.tetheringSettings[SETTINGS_WIFI_IP];
+    prefix = this.tetheringSettings[SETTINGS_WIFI_PREFIX];
+    dhcpStartIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_STARTIP];
+    dhcpEndIp = this.tetheringSettings[SETTINGS_WIFI_DHCPSERVER_ENDIP];
+    dns1 = this.tetheringSettings[SETTINGS_WIFI_DNS1];
+    dns2 = this.tetheringSettings[SETTINGS_WIFI_DNS2];
+
+    // Check the format to prevent netd from crash.
+    if (!ssid || ssid == "") {
+      debug("Invalid SSID value.");
+      return null;
+    }
+    if (securityType != WIFI_SECURITY_TYPE_NONE &&
+        securityType != WIFI_SECURITY_TYPE_WPA_PSK &&
+        securityType != WIFI_SECURITY_TYPE_WPA2_PSK) {
+
+      debug("Invalid security type.");
+      return null;
+    }
+    if (securityType != WIFI_SECURITY_TYPE_NONE && !securityId) {
+      debug("Invalid security password.");
+      return null;
+    }
+    // Using the default values here until application supports these settings.
+    if (interfaceIp == "" || prefix == "" ||
+        dhcpStartIp == "" || dhcpEndIp == "") {
+      debug("Invalid subnet information.");
+      return null;
+    }
+
+    return {
+      ssid: ssid,
+      security: securityType,
+      key: securityId,
+      ip: interfaceIp,
+      prefix: prefix,
+      startIp: dhcpStartIp,
+      endIp: dhcpEndIp,
+      dns1: dns1,
+      dns2: dns2,
+      enable: enable,
+      mode: enable ? WIFI_FIRMWARE_AP : WIFI_FIRMWARE_STATION,
+      link: enable ? NETWORK_INTERFACE_UP : NETWORK_INTERFACE_DOWN
+    };
+  },
+
   setWifiApEnabled: function(enabled, callback) {
-    WifiManager.setWifiApEnabled(enabled, callback);
+    let configuration = this.getWifiTetheringParameters(enabled);
+
+    if (!configuration) {
+      debug("Invalid Wifi Tethering configuration.");
+      return;
+    }
+
+    WifiManager.setWifiApEnabled(enabled, configuration, callback);
   },
 
   associate: function(msg) {
     const MAX_PRIORITY = 9999;
     const message = "WifiManager:associate:Return";
     let network = msg.data;
     if (!WifiManager.enabled) {
       this._sendMessage(message, false, "Wifi is disabled", msg);
@@ -2844,59 +2996,57 @@ WifiWorker.prototype = {
       }
     }
   },
 
   notifyTetheringOn: function notifyTetheringOn() {
     // It's really sad that we don't have an API to notify the wifi
     // hotspot status. Toggle settings to let gaia know that wifi hotspot
     // is enabled.
+    this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = true;
     gSettingsService.createLock().set(
-      "tethering.wifi.enabled", true, null, "fromInternalSetting");
+      SETTINGS_WIFI_TETHERING_ENABLED, true, null, "fromInternalSetting");
     // Check for the next request.
     this.nextRequest();
   },
 
   notifyTetheringOff: function notifyTetheringOff() {
     // It's really sad that we don't have an API to notify the wifi
     // hotspot status. Toggle settings to let gaia know that wifi hotspot
     // is disabled.
+    this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
     gSettingsService.createLock().set(
-      "tethering.wifi.enabled", false, null, "fromInternalSetting");
+      SETTINGS_WIFI_TETHERING_ENABLED, false, null, "fromInternalSetting");
     // Check for the next request.
     this.nextRequest();
   },
 
   handleWifiEnabled: function(enabled) {
     if (WifiManager.enabled === enabled) {
       return;
     }
     // Make sure Wifi hotspot is idle before switching to Wifi mode.
-    if (enabled && (gNetworkManager.wifiTetheringEnabled ||
+    if (enabled && (this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] ||
          WifiManager.tetheringState != "UNINITIALIZED")) {
       this.queueRequest(false, function(data) {
         this.disconnectedByWifi = true;
         this.setWifiApEnabled(false, this.notifyTetheringOff.bind(this));
       }.bind(this));
     }
     this.setWifiEnabled({enabled: enabled});
-    
+
     if (!enabled && this.disconnectedByWifi) {
       this.queueRequest(true, function(data) {
         this.disconnectedByWifi = false;
         this.setWifiApEnabled(true, this.notifyTetheringOn.bind(this));
       }.bind(this));
     }
   },
 
   handleWifiTetheringEnabled: function(enabled) {
-    if (gNetworkManager.wifiTetheringEnabled === enabled) {
-      return;
-    }
-
     // Make sure Wifi is idle before switching to Wifi hotspot mode.
     if (enabled && (WifiManager.enabled ||
          WifiManager.state != "UNINITIALIZED")) {
       this.disconnectedByWifiTethering = true;
       this.setWifiEnabled({enabled: false});
     }
 
     this.queueRequest(enabled, function(data) {
@@ -2915,40 +3065,84 @@ WifiWorker.prototype = {
     // so we need to carefully check if we have the one we're interested in.
     // The string we're interested in will be a JSON string that looks like:
     // {"key":"wifi.enabled","value":"true"}.
     if (topic !== kMozSettingsChangedObserverTopic) {
       return;
     }
 
     let setting = JSON.parse(data);
-    if (setting.key === "wifi.debugging.enabled") {
-      DEBUG = setting.value;
-      updateDebug();
-      return;
-    }
-    if (setting.key !== "wifi.enabled" &&
-        setting.key !== "tethering.wifi.enabled") {
-      return;
-    }
     // To avoid WifiWorker setting the wifi again, don't need to deal with
     // the "mozsettings-changed" event fired from internal setting.
     if (setting.message && setting.message === "fromInternalSetting") {
       return;
     }
 
-    switch (setting.key) {
-      case "wifi.enabled":
-        this.handleWifiEnabled(setting.value)
+    this.handle(setting.key, setting.value);
+  },
+
+  handle: function handle(aName, aResult) {
+    switch(aName) {
+      case SETTINGS_WIFI_ENABLED:
+        this.handleWifiEnabled(aResult)
+        break;
+      case SETTINGS_WIFI_DEBUG_ENABLED:
+        if (aResult === null)
+          aResult = false;
+        DEBUG = aResult;
+        updateDebug();
         break;
-      case "tethering.wifi.enabled":
-        this.handleWifiTetheringEnabled(setting.value)
+      case SETTINGS_WIFI_TETHERING_ENABLED:
+        this._oldWifiTetheringEnabledState = this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED];
+        // Fall through!
+      case SETTINGS_WIFI_SSID:
+      case SETTINGS_WIFI_SECURITY_TYPE:
+      case SETTINGS_WIFI_SECURITY_PASSWORD:
+      case SETTINGS_WIFI_IP:
+      case SETTINGS_WIFI_PREFIX:
+      case SETTINGS_WIFI_DHCPSERVER_STARTIP:
+      case SETTINGS_WIFI_DHCPSERVER_ENDIP:
+      case SETTINGS_WIFI_DNS1:
+      case SETTINGS_WIFI_DNS2:
+        if (aResult !== null) {
+          this.tetheringSettings[aName] = aResult;
+        }
+        debug("'" + aName + "'" + " is now " + this.tetheringSettings[aName]);
+        let index = this._wifiTetheringSettingsToRead.indexOf(aName);
+
+        if (index != -1) {
+          this._wifiTetheringSettingsToRead.splice(index, 1);
+        }
+
+        if (this._wifiTetheringSettingsToRead.length) {
+          debug("We haven't read completely the wifi Tethering data from settings db.");
+          break;
+        }
+
+        if (this._oldWifiTetheringEnabledState === this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
+          debug("No changes for SETTINGS_WIFI_TETHERING_ENABLED flag. Nothing to do.");
+          break;
+        }
+
+        if (this._oldWifiTetheringEnabledState === null &&
+            !this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED]) {
+          debug("Do nothing when initial settings for SETTINGS_WIFI_TETHERING_ENABLED flag is false.");
+          break;
+        }
+
+        this.handleWifiTetheringEnabled(aResult)
         break;
-    }
-  }
+    };
+  },
+
+  handleError: function handleError(aErrorMessage) {
+    debug("There was an error while reading Tethering settings.");
+    this.tetheringSettings = {};
+    this.tetheringSettings[SETTINGS_WIFI_TETHERING_ENABLED] = false;
+  },
 };
 
 this.NSGetFactory = XPCOMUtils.generateNSGetFactory([WifiWorker]);
 
 let debug;
 function updateDebug() {
   if (DEBUG) {
     debug = function (s) {
--- a/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
+++ b/gfx/layers/ipc/ShadowLayerUtilsGralloc.cpp
@@ -20,16 +20,18 @@
 
 #include "nsIMemoryReporter.h"
 
 #include "gfxImageSurface.h"
 #include "gfxPlatform.h"
 
 #include "GeckoProfiler.h"
 
+#include "cutils/properties.h"
+
 using namespace android;
 using namespace base;
 using namespace mozilla::layers;
 using namespace mozilla::gl;
 
 namespace IPC {
 
 void
@@ -338,16 +340,28 @@ ShadowLayerForwarder::AllocGrallocBuffer
 }
 
 bool
 ISurfaceAllocator::PlatformAllocSurfaceDescriptor(const gfxIntSize& aSize,
                                                   gfxASurface::gfxContentType aContent,
                                                   uint32_t aCaps,
                                                   SurfaceDescriptor* aBuffer)
 {
+
+  // Check for Nexus S to disable gralloc. We only check for this on ICS or
+  // earlier, in hopes that JB will work.
+#ifdef ANDROID_VERSION <= 15
+  char propValue[PROPERTY_VALUE_MAX];
+  property_get("ro.product.device", propValue, "None");
+  if (strcmp("crespo",propValue) == 0) {
+    NS_WARNING("Nexus S has issues with gralloc, falling back to shmem");
+    return false;
+  }
+#endif
+
   // Some GL implementations fail to render gralloc textures with
   // width < 64.  There's not much point in gralloc'ing buffers that
   // small anyway, so fall back on shared memory plus a texture
   // upload.
   if (aSize.width < 64) {
     return false;
   }
   PROFILER_LABEL("ShadowLayerForwarder", "PlatformAllocSurfaceDescriptor");
--- a/hal/gonk/GonkFMRadio.cpp
+++ b/hal/gonk/GonkFMRadio.cpp
@@ -218,18 +218,22 @@ runTavaruaRadio(void *)
       if (errno == EINTR)
         continue;
       break;
     }
 
     for (unsigned int i = 0; i < buffer.bytesused; i++) {
       switch (buf[i]) {
       case TAVARUA_EVT_RADIO_READY:
-        NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
-                                                hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
+        // The driver sends RADIO_READY both when we turn the radio on and when we turn 
+        // the radio off.
+        if (sRadioEnabled) {
+          NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_ENABLE,
+                                                  hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
+        }
         break;
       case TAVARUA_EVT_SEEK_COMPLETE:
         NS_DispatchToMainThread(new RadioUpdate(hal::FM_RADIO_OPERATION_SEEK,
                                                 hal::FM_RADIO_OPERATION_STATUS_SUCCESS));
         break;
       default:
         break;
       }
--- a/layout/build/Makefile.in
+++ b/layout/build/Makefile.in
@@ -34,16 +34,17 @@ SHARED_LIBRARY_LIBS = \
 	../xul/base/src/$(LIB_PREFIX)gkxulbase_s.$(LIB_SUFFIX) \
 	../mathml/$(LIB_PREFIX)gkmathml_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/base/src/$(LIB_PREFIX)gkconbase_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/canvas/src/$(LIB_PREFIX)gkconcvs_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/events/src/$(LIB_PREFIX)gkconevents_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/html/content/src/$(LIB_PREFIX)gkconhtmlcon_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/html/document/src/$(LIB_PREFIX)gkconhtmldoc_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/media/$(LIB_PREFIX)gkconmedia_s.$(LIB_SUFFIX) \
+	$(DEPTH)/content/media/encoder/$(LIB_PREFIX)gkconencoder_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/media/webaudio/$(LIB_PREFIX)gkconwebaudio_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/media/webaudio/blink/$(LIB_PREFIX)gkconwebaudio_blink_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/media/webrtc/$(LIB_PREFIX)gkconwebrtc_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xml/content/src/$(LIB_PREFIX)gkconxmlcon_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xml/document/src/$(LIB_PREFIX)gkconxmldoc_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xslt/src/base/$(LIB_PREFIX)txbase_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xslt/src/xml/$(LIB_PREFIX)txxml_s.$(LIB_SUFFIX) \
 	$(DEPTH)/content/xslt/src/xpath/$(LIB_PREFIX)txxpath_s.$(LIB_SUFFIX) \
--- a/layout/media/symbols.def.in
+++ b/layout/media/symbols.def.in
@@ -52,19 +52,23 @@ vpx_codec_encode
 #endif
 #endif
 #ifdef MOZ_VORBIS
 ogg_page_bos
 ogg_page_granulepos
 ogg_page_serialno
 ogg_stream_check
 ogg_stream_clear
+ogg_stream_eos
+ogg_stream_flush
 ogg_stream_init
+ogg_stream_packetin
 ogg_stream_packetout
 ogg_stream_pagein
+ogg_stream_pageout
 ogg_stream_reset
 ogg_sync_buffer
 ogg_sync_clear
 ogg_sync_init
 ogg_sync_pageseek
 ogg_sync_reset
 ogg_sync_wrote
 vorbis_block_clear
--- a/media/mtransport/objs.mk
+++ b/media/mtransport/objs.mk
@@ -74,16 +74,16 @@ MTRANSPORT_LCPPSRCS = \
   transportlayer.cpp \
   transportlayerice.cpp \
   transportlayerdtls.cpp \
   transportlayerlog.cpp \
   transportlayerloopback.cpp \
   transportlayerprsock.cpp \
   $(NULL)
 
-ifdef MOZ_B2G_RIL
+ifeq (gonk,$(MOZ_WIDGET_TOOLKIT))
 MTRANSPORT_LCPPSRCS += \
   gonk_addrs.cpp \
   $(NULL)
 endif
 
 MTRANSPORT_CPPSRCS = $(addprefix $(topsrcdir)/media/mtransport/, $(MTRANSPORT_LCPPSRCS))