Add support for mmap stream

Add support for MMAP streams created by Oboe service.
- A new audio thread class MmapThread deriving from ThreadBase
is added to manage permissions, volume, routing and wakelocks for activity
on MMAP streams.
- Requests received over MmapStreamInterface to open, start and stop
a MMAP stream are forwarded to audio policy manager so that activity
on MMAP capture and playback streams is visible to audio policy and
taken into account in volume and routing management.
`

Bug: Bug: 33398120
Test: manual test
Change-Id: I957906495fe9277d2972d810b633f50cd1e14ec3
diff --git a/services/audioflinger/AudioFlinger.cpp b/services/audioflinger/AudioFlinger.cpp
index 298d455..f875d0f 100644
--- a/services/audioflinger/AudioFlinger.cpp
+++ b/services/audioflinger/AudioFlinger.cpp
@@ -240,6 +240,7 @@
 }
 
 //static
+__attribute__ ((visibility ("default")))
 status_t MmapStreamInterface::openMmapStream(MmapStreamInterface::stream_direction_t direction,
                                              const audio_attributes_t *attr,
                                              audio_config_base_t *config,
@@ -261,19 +262,58 @@
     return ret;
 }
 
-status_t AudioFlinger::openMmapStream(MmapStreamInterface::stream_direction_t direction __unused,
-                                      const audio_attributes_t *attr __unused,
-                                      audio_config_base_t *config __unused,
-                                      const MmapStreamInterface::Client& client __unused,
-                                      audio_port_handle_t *deviceId __unused,
-                                      const sp<MmapStreamCallback>& callback __unused,
-                                      sp<MmapStreamInterface>& interface __unused)
+status_t AudioFlinger::openMmapStream(MmapStreamInterface::stream_direction_t direction,
+                                      const audio_attributes_t *attr,
+                                      audio_config_base_t *config,
+                                      const MmapStreamInterface::Client& client,
+                                      audio_port_handle_t *deviceId,
+                                      const sp<MmapStreamCallback>& callback,
+                                      sp<MmapStreamInterface>& interface)
 {
     status_t ret = initCheck();
     if (ret != NO_ERROR) {
         return ret;
     }
 
+    audio_session_t sessionId = (audio_session_t) newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
+    audio_stream_type_t streamType = AUDIO_STREAM_DEFAULT;
+    audio_io_handle_t io;
+    audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE;
+    if (direction == MmapStreamInterface::DIRECTION_OUTPUT) {
+        audio_config_t fullConfig = AUDIO_CONFIG_INITIALIZER;
+        fullConfig.sample_rate = config->sample_rate;
+        fullConfig.channel_mask = config->channel_mask;
+        fullConfig.format = config->format;
+        ret = AudioSystem::getOutputForAttr(attr, &io,
+                                            sessionId,
+                                            &streamType, client.clientUid,
+                                            &fullConfig,
+                                            (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ | AUDIO_OUTPUT_FLAG_DIRECT),
+                                            *deviceId, &portId);
+    } else {
+        ret = AudioSystem::getInputForAttr(attr, &io,
+                                              sessionId,
+                                              client.clientPid,
+                                              client.clientUid,
+                                              config,
+                                              AUDIO_INPUT_FLAG_MMAP_NOIRQ, *deviceId, &portId);
+    }
+    if (ret != NO_ERROR) {
+        return ret;
+    }
+
+    // at this stage, a MmapThread was created when openOutput() or openInput() was called by
+    // audio policy manager and we can retrieve it
+    sp<MmapThread> thread = mMmapThreads.valueFor(io);
+    if (thread != 0) {
+        interface = new MmapThreadHandle(thread);
+        thread->configure(attr, streamType, sessionId, callback, portId);
+    } else {
+        ret = NO_INIT;
+    }
+
+    ALOGV("%s done status %d portId %d", __FUNCTION__, ret, portId);
+
     return ret;
 }
 
@@ -441,6 +481,11 @@
             mRecordThreads.valueAt(i)->dump(fd, args);
         }
 
+        // dump mmap threads
+        for (size_t i = 0; i < mMmapThreads.size(); i++) {
+            mMmapThreads.valueAt(i)->dump(fd, args);
+        }
+
         // dump orphan effect chains
         if (mOrphanEffectChains.size() != 0) {
             write(fd, "  Orphan Effect Chains\n", strlen("  Orphan Effect Chains\n"));
@@ -948,11 +993,9 @@
     // assigned to HALs which do not have master mute support will apply master
     // mute during the mix operation.  Threads with HALs which do support master
     // mute will simply ignore the setting.
-    for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
-        if (mPlaybackThreads.valueAt(i)->isDuplicating()) {
-            continue;
-        }
-        mPlaybackThreads.valueAt(i)->setMasterMute(muted);
+    Vector<VolumeInterface *> volumeInterfaces = getAllVolumeInterfaces_l();
+    for (size_t i = 0; i < volumeInterfaces.size(); i++) {
+        volumeInterfaces[i]->setMasterMute(muted);
     }
 
     return NO_ERROR;
@@ -983,12 +1026,12 @@
 status_t AudioFlinger::checkStreamType(audio_stream_type_t stream) const
 {
     if (uint32_t(stream) >= AUDIO_STREAM_CNT) {
-        ALOGW("setStreamVolume() invalid stream %d", stream);
+        ALOGW("checkStreamType() invalid stream %d", stream);
         return BAD_VALUE;
     }
     pid_t caller = IPCThreadState::self()->getCallingPid();
     if (uint32_t(stream) >= AUDIO_STREAM_PUBLIC_CNT && caller != getpid_cached) {
-        ALOGW("setStreamVolume() pid %d cannot use internal stream type %d", caller, stream);
+        ALOGW("checkStreamType() pid %d cannot use internal stream type %d", caller, stream);
         return PERMISSION_DENIED;
     }
 
@@ -1010,22 +1053,22 @@
     ALOG_ASSERT(stream != AUDIO_STREAM_PATCH, "attempt to change AUDIO_STREAM_PATCH volume");
 
     AutoMutex lock(mLock);
-    PlaybackThread *thread = NULL;
+    Vector<VolumeInterface *> volumeInterfaces;
     if (output != AUDIO_IO_HANDLE_NONE) {
-        thread = checkPlaybackThread_l(output);
-        if (thread == NULL) {
+        VolumeInterface *volumeInterface = getVolumeInterface_l(output);
+        if (volumeInterface == NULL) {
             return BAD_VALUE;
         }
+        volumeInterfaces.add(volumeInterface);
     }
 
     mStreamTypes[stream].volume = value;
 
-    if (thread == NULL) {
-        for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
-            mPlaybackThreads.valueAt(i)->setStreamVolume(stream, value);
-        }
-    } else {
-        thread->setStreamVolume(stream, value);
+    if (volumeInterfaces.size() == 0) {
+        volumeInterfaces = getAllVolumeInterfaces_l();
+    }
+    for (size_t i = 0; i < volumeInterfaces.size(); i++) {
+        volumeInterfaces[i]->setStreamVolume(stream, value);
     }
 
     return NO_ERROR;
@@ -1051,8 +1094,10 @@
 
     AutoMutex lock(mLock);
     mStreamTypes[stream].mute = muted;
-    for (size_t i = 0; i < mPlaybackThreads.size(); i++)
-        mPlaybackThreads.valueAt(i)->setStreamMute(stream, muted);
+    Vector<VolumeInterface *> volumeInterfaces = getAllVolumeInterfaces_l();
+    for (size_t i = 0; i < volumeInterfaces.size(); i++) {
+        volumeInterfaces[i]->setStreamMute(stream, muted);
+    }
 
     return NO_ERROR;
 }
@@ -1067,11 +1112,12 @@
     AutoMutex lock(mLock);
     float volume;
     if (output != AUDIO_IO_HANDLE_NONE) {
-        PlaybackThread *thread = checkPlaybackThread_l(output);
-        if (thread == NULL) {
-            return 0.0f;
+        VolumeInterface *volumeInterface = getVolumeInterface_l(output);
+        if (volumeInterface != NULL) {
+            volume = volumeInterface->streamVolume(stream);
+        } else {
+            volume = 0.0f;
         }
-        volume = thread->streamVolume(stream);
     } else {
         volume = streamVolume_l(stream);
     }
@@ -1172,6 +1218,9 @@
         thread = checkPlaybackThread_l(ioHandle);
         if (thread == 0) {
             thread = checkRecordThread_l(ioHandle);
+            if (thread == 0) {
+                thread = checkMmapThread_l(ioHandle);
+            }
         } else if (thread == primaryPlaybackThread_l()) {
             // indicate output device change to all input threads for pre processing
             AudioParameter param = AudioParameter(keyValuePairs);
@@ -1213,15 +1262,17 @@
         return out_s8;
     }
 
-    PlaybackThread *playbackThread = checkPlaybackThread_l(ioHandle);
-    if (playbackThread != NULL) {
-        return playbackThread->getParameters(keys);
+    ThreadBase *thread = (ThreadBase *)checkPlaybackThread_l(ioHandle);
+    if (thread == NULL) {
+        thread = (ThreadBase *)checkRecordThread_l(ioHandle);
+        if (thread == NULL) {
+            thread = (ThreadBase *)checkMmapThread_l(ioHandle);
+            if (thread == NULL) {
+                String8("");
+            }
+        }
     }
-    RecordThread *recordThread = checkRecordThread_l(ioHandle);
-    if (recordThread != NULL) {
-        return recordThread->getParameters(keys);
-    }
-    return String8("");
+    return thread->getParameters(keys);
 }
 
 size_t AudioFlinger::getInputBufferSize(uint32_t sampleRate, audio_format_t format,
@@ -1827,7 +1878,7 @@
 // ----------------------------------------------------------------------------
 
 
-sp<AudioFlinger::PlaybackThread> AudioFlinger::openOutput_l(audio_module_handle_t module,
+sp<AudioFlinger::ThreadBase> AudioFlinger::openOutput_l(audio_module_handle_t module,
                                                             audio_io_handle_t *output,
                                                             audio_config_t *config,
                                                             audio_devices_t devices,
@@ -1883,22 +1934,34 @@
     mHardwareStatus = AUDIO_HW_IDLE;
 
     if (status == NO_ERROR) {
-
-        PlaybackThread *thread;
-        if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
-            thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
-            ALOGV("openOutput_l() created offload output: ID %d thread %p", *output, thread);
-        } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
-                || !isValidPcmSinkFormat(config->format)
-                || !isValidPcmSinkChannelMask(config->channel_mask)) {
-            thread = new DirectOutputThread(this, outputStream, *output, devices, mSystemReady);
-            ALOGV("openOutput_l() created direct output: ID %d thread %p", *output, thread);
+        if (flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) {
+            sp<MmapPlaybackThread> thread =
+                    new MmapPlaybackThread(this, *output, outHwDev, outputStream,
+                                          devices, AUDIO_DEVICE_NONE, mSystemReady);
+            mMmapThreads.add(*output, thread);
+            ALOGV("openOutput_l() created mmap playback thread: ID %d thread %p",
+                  *output, thread.get());
+            return thread;
         } else {
-            thread = new MixerThread(this, outputStream, *output, devices, mSystemReady);
-            ALOGV("openOutput_l() created mixer output: ID %d thread %p", *output, thread);
+            sp<PlaybackThread> thread;
+            if (flags & AUDIO_OUTPUT_FLAG_COMPRESS_OFFLOAD) {
+                thread = new OffloadThread(this, outputStream, *output, devices, mSystemReady);
+                ALOGV("openOutput_l() created offload output: ID %d thread %p",
+                      *output, thread.get());
+            } else if ((flags & AUDIO_OUTPUT_FLAG_DIRECT)
+                    || !isValidPcmSinkFormat(config->format)
+                    || !isValidPcmSinkChannelMask(config->channel_mask)) {
+                thread = new DirectOutputThread(this, outputStream, *output, devices, mSystemReady);
+                ALOGV("openOutput_l() created direct output: ID %d thread %p",
+                      *output, thread.get());
+            } else {
+                thread = new MixerThread(this, outputStream, *output, devices, mSystemReady);
+                ALOGV("openOutput_l() created mixer output: ID %d thread %p",
+                      *output, thread.get());
+            }
+            mPlaybackThreads.add(*output, thread);
+            return thread;
         }
-        mPlaybackThreads.add(*output, thread);
-        return thread;
     }
 
     return 0;
@@ -1912,8 +1975,8 @@
                                   uint32_t *latencyMs,
                                   audio_output_flags_t flags)
 {
-    ALOGI("openOutput(), module %d Device %x, SamplingRate %d, Format %#08x, Channels %x, flags %x",
-              module,
+    ALOGI("openOutput() this %p, module %d Device %x, SamplingRate %d, Format %#08x, Channels %x, flags %x",
+              this, module,
               (devices != NULL) ? *devices : 0,
               config->sample_rate,
               config->format,
@@ -1926,22 +1989,28 @@
 
     Mutex::Autolock _l(mLock);
 
-    sp<PlaybackThread> thread = openOutput_l(module, output, config, *devices, address, flags);
+    sp<ThreadBase> thread = openOutput_l(module, output, config, *devices, address, flags);
     if (thread != 0) {
-        *latencyMs = thread->latency();
+        if ((flags & AUDIO_OUTPUT_FLAG_MMAP_NOIRQ) == 0) {
+            PlaybackThread *playbackThread = (PlaybackThread *)thread.get();
+            *latencyMs = playbackThread->latency();
 
-        // notify client processes of the new output creation
-        thread->ioConfigChanged(AUDIO_OUTPUT_OPENED);
+            // notify client processes of the new output creation
+            playbackThread->ioConfigChanged(AUDIO_OUTPUT_OPENED);
 
-        // the first primary output opened designates the primary hw device
-        if ((mPrimaryHardwareDev == NULL) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
-            ALOGI("Using module %d has the primary audio interface", module);
-            mPrimaryHardwareDev = thread->getOutput()->audioHwDev;
+            // the first primary output opened designates the primary hw device
+            if ((mPrimaryHardwareDev == NULL) && (flags & AUDIO_OUTPUT_FLAG_PRIMARY)) {
+                ALOGI("Using module %d has the primary audio interface", module);
+                mPrimaryHardwareDev = playbackThread->getOutput()->audioHwDev;
 
-            AutoMutex lock(mHardwareLock);
-            mHardwareStatus = AUDIO_HW_SET_MODE;
-            mPrimaryHardwareDev->hwDevice()->setMode(mMode);
-            mHardwareStatus = AUDIO_HW_IDLE;
+                AutoMutex lock(mHardwareLock);
+                mHardwareStatus = AUDIO_HW_SET_MODE;
+                mPrimaryHardwareDev->hwDevice()->setMode(mMode);
+                mHardwareStatus = AUDIO_HW_IDLE;
+            }
+        } else {
+            MmapThread *mmapThread = (MmapThread *)thread.get();
+            mmapThread->ioConfigChanged(AUDIO_OUTPUT_OPENED);
         }
         return NO_ERROR;
     }
@@ -1980,54 +2049,68 @@
 {
     // keep strong reference on the playback thread so that
     // it is not destroyed while exit() is executed
-    sp<PlaybackThread> thread;
+    sp<PlaybackThread> playbackThread;
+    sp<MmapPlaybackThread> mmapThread;
     {
         Mutex::Autolock _l(mLock);
-        thread = checkPlaybackThread_l(output);
-        if (thread == NULL) {
-            return BAD_VALUE;
-        }
+        playbackThread = checkPlaybackThread_l(output);
+        if (playbackThread != NULL) {
+            ALOGV("closeOutput() %d", output);
 
-        ALOGV("closeOutput() %d", output);
-
-        if (thread->type() == ThreadBase::MIXER) {
-            for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
-                if (mPlaybackThreads.valueAt(i)->isDuplicating()) {
-                    DuplicatingThread *dupThread =
-                            (DuplicatingThread *)mPlaybackThreads.valueAt(i).get();
-                    dupThread->removeOutputTrack((MixerThread *)thread.get());
+            if (playbackThread->type() == ThreadBase::MIXER) {
+                for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
+                    if (mPlaybackThreads.valueAt(i)->isDuplicating()) {
+                        DuplicatingThread *dupThread =
+                                (DuplicatingThread *)mPlaybackThreads.valueAt(i).get();
+                        dupThread->removeOutputTrack((MixerThread *)playbackThread.get());
+                    }
                 }
             }
-        }
 
 
-        mPlaybackThreads.removeItem(output);
-        // save all effects to the default thread
-        if (mPlaybackThreads.size()) {
-            PlaybackThread *dstThread = checkPlaybackThread_l(mPlaybackThreads.keyAt(0));
-            if (dstThread != NULL) {
-                // audioflinger lock is held here so the acquisition order of thread locks does not
-                // matter
-                Mutex::Autolock _dl(dstThread->mLock);
-                Mutex::Autolock _sl(thread->mLock);
-                Vector< sp<EffectChain> > effectChains = thread->getEffectChains_l();
-                for (size_t i = 0; i < effectChains.size(); i ++) {
-                    moveEffectChain_l(effectChains[i]->sessionId(), thread.get(), dstThread, true);
+            mPlaybackThreads.removeItem(output);
+            // save all effects to the default thread
+            if (mPlaybackThreads.size()) {
+                PlaybackThread *dstThread = checkPlaybackThread_l(mPlaybackThreads.keyAt(0));
+                if (dstThread != NULL) {
+                    // audioflinger lock is held here so the acquisition order of thread locks does not
+                    // matter
+                    Mutex::Autolock _dl(dstThread->mLock);
+                    Mutex::Autolock _sl(playbackThread->mLock);
+                    Vector< sp<EffectChain> > effectChains = playbackThread->getEffectChains_l();
+                    for (size_t i = 0; i < effectChains.size(); i ++) {
+                        moveEffectChain_l(effectChains[i]->sessionId(), playbackThread.get(), dstThread, true);
+                    }
                 }
             }
+        } else {
+            mmapThread = (MmapPlaybackThread *)checkMmapThread_l(output);
+            if (mmapThread == 0) {
+                return BAD_VALUE;
+            }
+            mMmapThreads.removeItem(output);
+            ALOGV("closing mmapThread %p", mmapThread.get());
         }
         const sp<AudioIoDescriptor> ioDesc = new AudioIoDescriptor();
         ioDesc->mIoHandle = output;
         ioConfigChanged(AUDIO_OUTPUT_CLOSED, ioDesc);
     }
-    thread->exit();
     // The thread entity (active unit of execution) is no longer running here,
     // but the ThreadBase container still exists.
 
-    if (!thread->isDuplicating()) {
-        closeOutputFinish(thread);
+    if (playbackThread != 0) {
+        playbackThread->exit();
+        if (!playbackThread->isDuplicating()) {
+            closeOutputFinish(playbackThread);
+        }
+    } else if (mmapThread != 0) {
+        ALOGV("mmapThread exit()");
+        mmapThread->exit();
+        AudioStreamOut *out = mmapThread->clearOutput();
+        ALOG_ASSERT(out != NULL, "out shouldn't be NULL");
+        // from now on thread->mOutput is NULL
+        delete out;
     }
-
     return NO_ERROR;
 }
 
@@ -2091,7 +2174,7 @@
         return BAD_VALUE;
     }
 
-    sp<RecordThread> thread = openInput_l(module, input, config, *devices, address, source, flags);
+    sp<ThreadBase> thread = openInput_l(module, input, config, *devices, address, source, flags);
 
     if (thread != 0) {
         // notify client processes of the new input creation
@@ -2101,7 +2184,7 @@
     return NO_INIT;
 }
 
-sp<AudioFlinger::RecordThread> AudioFlinger::openInput_l(audio_module_handle_t module,
+sp<AudioFlinger::ThreadBase> AudioFlinger::openInput_l(audio_module_handle_t module,
                                                          audio_io_handle_t *input,
                                                          audio_config_t *config,
                                                          audio_devices_t devices,
@@ -2161,74 +2244,82 @@
     }
 
     if (status == NO_ERROR && inStream != 0) {
-
-#ifdef TEE_SINK
-        // Try to re-use most recently used Pipe to archive a copy of input for dumpsys,
-        // or (re-)create if current Pipe is idle and does not match the new format
-        sp<NBAIO_Sink> teeSink;
-        enum {
-            TEE_SINK_NO,    // don't copy input
-            TEE_SINK_NEW,   // copy input using a new pipe
-            TEE_SINK_OLD,   // copy input using an existing pipe
-        } kind;
-        NBAIO_Format format = Format_from_SR_C(halconfig.sample_rate,
-                audio_channel_count_from_in_mask(halconfig.channel_mask), halconfig.format);
-        if (!mTeeSinkInputEnabled) {
-            kind = TEE_SINK_NO;
-        } else if (!Format_isValid(format)) {
-            kind = TEE_SINK_NO;
-        } else if (mRecordTeeSink == 0) {
-            kind = TEE_SINK_NEW;
-        } else if (mRecordTeeSink->getStrongCount() != 1) {
-            kind = TEE_SINK_NO;
-        } else if (Format_isEqual(format, mRecordTeeSink->format())) {
-            kind = TEE_SINK_OLD;
-        } else {
-            kind = TEE_SINK_NEW;
-        }
-        switch (kind) {
-        case TEE_SINK_NEW: {
-            Pipe *pipe = new Pipe(mTeeSinkInputFrames, format);
-            size_t numCounterOffers = 0;
-            const NBAIO_Format offers[1] = {format};
-            ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
-            ALOG_ASSERT(index == 0);
-            PipeReader *pipeReader = new PipeReader(*pipe);
-            numCounterOffers = 0;
-            index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
-            ALOG_ASSERT(index == 0);
-            mRecordTeeSink = pipe;
-            mRecordTeeSource = pipeReader;
-            teeSink = pipe;
-            }
-            break;
-        case TEE_SINK_OLD:
-            teeSink = mRecordTeeSink;
-            break;
-        case TEE_SINK_NO:
-        default:
-            break;
-        }
-#endif
-
         AudioStreamIn *inputStream = new AudioStreamIn(inHwDev, inStream, flags);
-
-        // Start record thread
-        // RecordThread requires both input and output device indication to forward to audio
-        // pre processing modules
-        sp<RecordThread> thread = new RecordThread(this,
-                                  inputStream,
-                                  *input,
-                                  primaryOutputDevice_l(),
-                                  devices,
-                                  mSystemReady
+        if ((flags & AUDIO_INPUT_FLAG_MMAP_NOIRQ) != 0) {
+            sp<MmapCaptureThread> thread =
+                    new MmapCaptureThread(this, *input,
+                                          inHwDev, inputStream,
+                                          primaryOutputDevice_l(), devices, mSystemReady);
+            mMmapThreads.add(*input, thread);
+            ALOGV("openInput_l() created mmap capture thread: ID %d thread %p", *input, thread.get());
+            return thread;
+        } else {
 #ifdef TEE_SINK
-                                  , teeSink
+            // Try to re-use most recently used Pipe to archive a copy of input for dumpsys,
+            // or (re-)create if current Pipe is idle and does not match the new format
+            sp<NBAIO_Sink> teeSink;
+            enum {
+                TEE_SINK_NO,    // don't copy input
+                TEE_SINK_NEW,   // copy input using a new pipe
+                TEE_SINK_OLD,   // copy input using an existing pipe
+            } kind;
+            NBAIO_Format format = Format_from_SR_C(halconfig.sample_rate,
+                    audio_channel_count_from_in_mask(halconfig.channel_mask), halconfig.format);
+            if (!mTeeSinkInputEnabled) {
+                kind = TEE_SINK_NO;
+            } else if (!Format_isValid(format)) {
+                kind = TEE_SINK_NO;
+            } else if (mRecordTeeSink == 0) {
+                kind = TEE_SINK_NEW;
+            } else if (mRecordTeeSink->getStrongCount() != 1) {
+                kind = TEE_SINK_NO;
+            } else if (Format_isEqual(format, mRecordTeeSink->format())) {
+                kind = TEE_SINK_OLD;
+            } else {
+                kind = TEE_SINK_NEW;
+            }
+            switch (kind) {
+            case TEE_SINK_NEW: {
+                Pipe *pipe = new Pipe(mTeeSinkInputFrames, format);
+                size_t numCounterOffers = 0;
+                const NBAIO_Format offers[1] = {format};
+                ssize_t index = pipe->negotiate(offers, 1, NULL, numCounterOffers);
+                ALOG_ASSERT(index == 0);
+                PipeReader *pipeReader = new PipeReader(*pipe);
+                numCounterOffers = 0;
+                index = pipeReader->negotiate(offers, 1, NULL, numCounterOffers);
+                ALOG_ASSERT(index == 0);
+                mRecordTeeSink = pipe;
+                mRecordTeeSource = pipeReader;
+                teeSink = pipe;
+                }
+                break;
+            case TEE_SINK_OLD:
+                teeSink = mRecordTeeSink;
+                break;
+            case TEE_SINK_NO:
+            default:
+                break;
+            }
 #endif
-                                  );
-        mRecordThreads.add(*input, thread);
-        ALOGV("openInput_l() created record thread: ID %d thread %p", *input, thread.get());
-        return thread;
+
+            // Start record thread
+            // RecordThread requires both input and output device indication to forward to audio
+            // pre processing modules
+            sp<RecordThread> thread = new RecordThread(this,
+                                      inputStream,
+                                      *input,
+                                      primaryOutputDevice_l(),
+                                      devices,
+                                      mSystemReady
+#ifdef TEE_SINK
+                                      , teeSink
+#endif
+                                      );
+            mRecordThreads.add(*input, thread);
+            ALOGV("openInput_l() created record thread: ID %d thread %p", *input, thread.get());
+            return thread;
+        }
     }
 
     *input = AUDIO_IO_HANDLE_NONE;
@@ -2244,60 +2335,73 @@
 {
     // keep strong reference on the record thread so that
     // it is not destroyed while exit() is executed
-    sp<RecordThread> thread;
+    sp<RecordThread> recordThread;
+    sp<MmapCaptureThread> mmapThread;
     {
         Mutex::Autolock _l(mLock);
-        thread = checkRecordThread_l(input);
-        if (thread == 0) {
-            return BAD_VALUE;
-        }
+        recordThread = checkRecordThread_l(input);
+        if (recordThread != 0) {
+            ALOGV("closeInput() %d", input);
 
-        ALOGV("closeInput() %d", input);
-
-        // If we still have effect chains, it means that a client still holds a handle
-        // on at least one effect. We must either move the chain to an existing thread with the
-        // same session ID or put it aside in case a new record thread is opened for a
-        // new capture on the same session
-        sp<EffectChain> chain;
-        {
-            Mutex::Autolock _sl(thread->mLock);
-            Vector< sp<EffectChain> > effectChains = thread->getEffectChains_l();
-            // Note: maximum one chain per record thread
-            if (effectChains.size() != 0) {
-                chain = effectChains[0];
-            }
-        }
-        if (chain != 0) {
-            // first check if a record thread is already opened with a client on the same session.
-            // This should only happen in case of overlap between one thread tear down and the
-            // creation of its replacement
-            size_t i;
-            for (i = 0; i < mRecordThreads.size(); i++) {
-                sp<RecordThread> t = mRecordThreads.valueAt(i);
-                if (t == thread) {
-                    continue;
-                }
-                if (t->hasAudioSession(chain->sessionId()) != 0) {
-                    Mutex::Autolock _l(t->mLock);
-                    ALOGV("closeInput() found thread %d for effect session %d",
-                          t->id(), chain->sessionId());
-                    t->addEffectChain_l(chain);
-                    break;
+            // If we still have effect chains, it means that a client still holds a handle
+            // on at least one effect. We must either move the chain to an existing thread with the
+            // same session ID or put it aside in case a new record thread is opened for a
+            // new capture on the same session
+            sp<EffectChain> chain;
+            {
+                Mutex::Autolock _sl(recordThread->mLock);
+                Vector< sp<EffectChain> > effectChains = recordThread->getEffectChains_l();
+                // Note: maximum one chain per record thread
+                if (effectChains.size() != 0) {
+                    chain = effectChains[0];
                 }
             }
-            // put the chain aside if we could not find a record thread with the same session id.
-            if (i == mRecordThreads.size()) {
-                putOrphanEffectChain_l(chain);
+            if (chain != 0) {
+                // first check if a record thread is already opened with a client on the same session.
+                // This should only happen in case of overlap between one thread tear down and the
+                // creation of its replacement
+                size_t i;
+                for (i = 0; i < mRecordThreads.size(); i++) {
+                    sp<RecordThread> t = mRecordThreads.valueAt(i);
+                    if (t == recordThread) {
+                        continue;
+                    }
+                    if (t->hasAudioSession(chain->sessionId()) != 0) {
+                        Mutex::Autolock _l(t->mLock);
+                        ALOGV("closeInput() found thread %d for effect session %d",
+                              t->id(), chain->sessionId());
+                        t->addEffectChain_l(chain);
+                        break;
+                    }
+                }
+                // put the chain aside if we could not find a record thread with the same session id.
+                if (i == mRecordThreads.size()) {
+                    putOrphanEffectChain_l(chain);
+                }
             }
+            mRecordThreads.removeItem(input);
+        } else {
+            mmapThread = (MmapCaptureThread *)checkMmapThread_l(input);
+            if (mmapThread == 0) {
+                return BAD_VALUE;
+            }
+            mMmapThreads.removeItem(input);
         }
         const sp<AudioIoDescriptor> ioDesc = new AudioIoDescriptor();
         ioDesc->mIoHandle = input;
         ioConfigChanged(AUDIO_INPUT_CLOSED, ioDesc);
-        mRecordThreads.removeItem(input);
     }
     // FIXME: calling thread->exit() without mLock held should not be needed anymore now that
     // we have a different lock for notification client
-    closeInputFinish(thread);
+    if (recordThread != 0) {
+        closeInputFinish(recordThread);
+    } else if (mmapThread != 0) {
+        mmapThread->exit();
+        AudioStreamIn *in = mmapThread->clearInput();
+        ALOG_ASSERT(in != NULL, "in shouldn't be NULL");
+        // from now on thread->mInput is NULL
+        delete in;
+    }
     return NO_ERROR;
 }
 
@@ -2325,7 +2429,9 @@
         PlaybackThread *thread = mPlaybackThreads.valueAt(i).get();
         thread->invalidateTracks(stream);
     }
-
+    for (size_t i = 0; i < mMmapThreads.size(); i++) {
+        mMmapThreads[i]->invalidateTracks(stream);
+    }
     return NO_ERROR;
 }
 
@@ -2475,16 +2581,18 @@
 // checkThread_l() must be called with AudioFlinger::mLock held
 AudioFlinger::ThreadBase *AudioFlinger::checkThread_l(audio_io_handle_t ioHandle) const
 {
-    ThreadBase *thread = NULL;
-    switch (audio_unique_id_get_use(ioHandle)) {
-    case AUDIO_UNIQUE_ID_USE_OUTPUT:
-        thread = checkPlaybackThread_l(ioHandle);
-        break;
-    case AUDIO_UNIQUE_ID_USE_INPUT:
-        thread = checkRecordThread_l(ioHandle);
-        break;
-    default:
-        break;
+    ThreadBase *thread = checkMmapThread_l(ioHandle);
+    if (thread == 0) {
+        switch (audio_unique_id_get_use(ioHandle)) {
+        case AUDIO_UNIQUE_ID_USE_OUTPUT:
+            thread = checkPlaybackThread_l(ioHandle);
+            break;
+        case AUDIO_UNIQUE_ID_USE_INPUT:
+            thread = checkRecordThread_l(ioHandle);
+            break;
+        default:
+            break;
+        }
     }
     return thread;
 }
@@ -2508,6 +2616,42 @@
     return mRecordThreads.valueFor(input).get();
 }
 
+// checkMmapThread_l() must be called with AudioFlinger::mLock held
+AudioFlinger::MmapThread *AudioFlinger::checkMmapThread_l(audio_io_handle_t io) const
+{
+    return mMmapThreads.valueFor(io).get();
+}
+
+
+// checkPlaybackThread_l() must be called with AudioFlinger::mLock held
+AudioFlinger::VolumeInterface *AudioFlinger::getVolumeInterface_l(audio_io_handle_t output) const
+{
+    VolumeInterface *volumeInterface = (VolumeInterface *)mPlaybackThreads.valueFor(output).get();
+    if (volumeInterface == nullptr) {
+        MmapThread *mmapThread = mMmapThreads.valueFor(output).get();
+        if (mmapThread != nullptr) {
+            if (mmapThread->isOutput()) {
+                volumeInterface = (VolumeInterface *)mmapThread;
+            }
+        }
+    }
+    return volumeInterface;
+}
+
+Vector <AudioFlinger::VolumeInterface *> AudioFlinger::getAllVolumeInterfaces_l() const
+{
+    Vector <VolumeInterface *> volumeInterfaces;
+    for (size_t i = 0; i < mPlaybackThreads.size(); i++) {
+        volumeInterfaces.add((VolumeInterface *)mPlaybackThreads.valueAt(i).get());
+    }
+    for (size_t i = 0; i < mMmapThreads.size(); i++) {
+        if (mMmapThreads.valueAt(i)->isOutput()) {
+            volumeInterfaces.add((VolumeInterface *)mMmapThreads.valueAt(i).get());
+        }
+    }
+    return volumeInterfaces;
+}
+
 audio_unique_id_t AudioFlinger::nextUniqueId(audio_unique_id_use_t use)
 {
     // This is the internal API, so it is OK to assert on bad parameter.
@@ -2799,7 +2943,7 @@
                     break;
                 }
             }
-            if (io == 0) {
+            if (io == AUDIO_IO_HANDLE_NONE) {
                 for (size_t i = 0; i < mRecordThreads.size(); i++) {
                     if (mRecordThreads.valueAt(i)->hasAudioSession(sessionId) != 0) {
                         io = mRecordThreads.keyAt(i);
@@ -2807,6 +2951,14 @@
                     }
                 }
             }
+            if (io == AUDIO_IO_HANDLE_NONE) {
+                for (size_t i = 0; i < mMmapThreads.size(); i++) {
+                    if (mMmapThreads.valueAt(i)->hasAudioSession(sessionId) != 0) {
+                        io = mMmapThreads.keyAt(i);
+                        break;
+                    }
+                }
+            }
             // If no output thread contains the requested session ID, default to
             // first output. The effect chain will be moved to the correct output
             // thread when a track with the same session ID is created
@@ -2819,9 +2971,12 @@
         if (thread == NULL) {
             thread = checkPlaybackThread_l(io);
             if (thread == NULL) {
-                ALOGE("createEffect() unknown output thread");
-                lStatus = BAD_VALUE;
-                goto Exit;
+                thread = checkMmapThread_l(io);
+                if (thread == NULL) {
+                    ALOGE("createEffect() unknown output thread");
+                    lStatus = BAD_VALUE;
+                    goto Exit;
+                }
             }
         } else {
             // Check if one effect chain was awaiting for an effect to be created on this
diff --git a/services/audioflinger/AudioFlinger.h b/services/audioflinger/AudioFlinger.h
index 3d6571e..cbd7c37 100644
--- a/services/audioflinger/AudioFlinger.h
+++ b/services/audioflinger/AudioFlinger.h
@@ -543,19 +543,40 @@
         void                stop_nonvirtual();
     };
 
+    // Mmap stream control interface implementation. Each MmapThreadHandle controls one
+    // MmapPlaybackThread or MmapCaptureThread instance.
+    class MmapThreadHandle : public MmapStreamInterface {
+    public:
+        explicit            MmapThreadHandle(const sp<MmapThread>& thread);
+        virtual             ~MmapThreadHandle();
+
+        // MmapStreamInterface virtuals
+        virtual status_t createMmapBuffer(int32_t minSizeFrames,
+                                          struct audio_mmap_buffer_info *info);
+        virtual status_t getMmapPosition(struct audio_mmap_position *position);
+        virtual status_t start(const MmapStreamInterface::Client& client, audio_port_handle_t *handle);
+        virtual status_t stop(audio_port_handle_t handle);
+
+    private:
+        sp<MmapThread> mThread;
+    };
 
               ThreadBase *checkThread_l(audio_io_handle_t ioHandle) const;
               PlaybackThread *checkPlaybackThread_l(audio_io_handle_t output) const;
               MixerThread *checkMixerThread_l(audio_io_handle_t output) const;
               RecordThread *checkRecordThread_l(audio_io_handle_t input) const;
-              sp<RecordThread> openInput_l(audio_module_handle_t module,
+              MmapThread *checkMmapThread_l(audio_io_handle_t io) const;
+              VolumeInterface *getVolumeInterface_l(audio_io_handle_t output) const;
+              Vector <VolumeInterface *> getAllVolumeInterfaces_l() const;
+
+              sp<ThreadBase> openInput_l(audio_module_handle_t module,
                                            audio_io_handle_t *input,
                                            audio_config_t *config,
                                            audio_devices_t device,
                                            const String8& address,
                                            audio_source_t source,
                                            audio_input_flags_t flags);
-              sp<PlaybackThread> openOutput_l(audio_module_handle_t module,
+              sp<ThreadBase> openOutput_l(audio_module_handle_t module,
                                               audio_io_handle_t *output,
                                               audio_config_t *config,
                                               audio_devices_t devices,
@@ -732,6 +753,12 @@
 
                 // list of sessions for which a valid HW A/V sync ID was retrieved from the HAL
                 DefaultKeyedVector< audio_session_t , audio_hw_sync_t >mHwAvSyncIds;
+
+                // list of MMAP stream control threads. Those threads allow for wake lock, routing
+                // and volume control for activity on the associated MMAP stream at the HAL.
+                // Audio data transfer is directly handled by the client creating the MMAP stream
+                DefaultKeyedVector< audio_io_handle_t, sp<MmapThread> >  mMmapThreads;
+
 private:
     sp<Client>  registerPid(pid_t pid);    // always returns non-0
 
diff --git a/services/audioflinger/Effects.cpp b/services/audioflinger/Effects.cpp
index 6c937a5..2f90a14 100644
--- a/services/audioflinger/Effects.cpp
+++ b/services/audioflinger/Effects.cpp
@@ -1197,10 +1197,9 @@
         mEnabled = false;
     } else {
         if (thread != 0) {
-            if (thread->type() == ThreadBase::OFFLOAD) {
-                PlaybackThread *t = (PlaybackThread *)thread.get();
-                Mutex::Autolock _l(t->mLock);
-                t->broadcast_l();
+            if (thread->type() == ThreadBase::OFFLOAD || thread->type() == ThreadBase::MMAP) {
+                Mutex::Autolock _l(thread->mLock);
+                thread->broadcast_l();
             }
             if (!effect->isOffloadable()) {
                 if (thread->type() == ThreadBase::OFFLOAD) {
@@ -1242,10 +1241,9 @@
     sp<ThreadBase> thread = effect->thread().promote();
     if (thread != 0) {
         thread->checkSuspendOnEffectEnabled(effect, false, effect->sessionId());
-        if (thread->type() == ThreadBase::OFFLOAD) {
-            PlaybackThread *t = (PlaybackThread *)thread.get();
-            Mutex::Autolock _l(t->mLock);
-            t->broadcast_l();
+        if (thread->type() == ThreadBase::OFFLOAD || thread->type() == ThreadBase::MMAP) {
+            Mutex::Autolock _l(thread->mLock);
+            thread->broadcast_l();
         }
     }
 
@@ -1556,6 +1554,9 @@
 // Must be called with EffectChain::mLock locked
 void AudioFlinger::EffectChain::clearInputBuffer_l(const sp<ThreadBase>& thread)
 {
+    if (mInBuffer == NULL) {
+        return;
+    }
     // TODO: This will change in the future, depending on multichannel
     // and sample format changes for effects.
     // Currently effects processing is only available for stereo, AUDIO_FORMAT_PCM_16_BIT
diff --git a/services/audioflinger/MmapTracks.h b/services/audioflinger/MmapTracks.h
new file mode 100644
index 0000000..e4fe8ac
--- /dev/null
+++ b/services/audioflinger/MmapTracks.h
@@ -0,0 +1,60 @@
+/*
+**
+** Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+
+#ifndef INCLUDING_FROM_AUDIOFLINGER_H
+    #error This header file should only be included from AudioFlinger.h
+#endif
+
+// playback track
+class MmapTrack : public TrackBase {
+public:
+                MmapTrack(ThreadBase *thread,
+                            uint32_t sampleRate,
+                            audio_format_t format,
+                            audio_channel_mask_t channelMask,
+                            audio_session_t sessionId,
+                            uid_t uid,
+                            audio_port_handle_t portId = AUDIO_PORT_HANDLE_NONE);
+    virtual             ~MmapTrack();
+
+                        // TrackBase virtual
+    virtual status_t    initCheck() const;
+    virtual status_t    start(AudioSystem::sync_event_t event,
+                              audio_session_t triggerSession);
+    virtual void        stop();
+    virtual bool        isFastTrack() const { return false; }
+
+     static void        appendDumpHeader(String8& result);
+            void        dump(char* buffer, size_t size);
+
+protected:
+    friend class MmapThread;
+
+                MmapTrack(const MmapTrack&);
+                MmapTrack& operator = (const MmapTrack&);
+
+    // AudioBufferProvider interface
+    virtual status_t getNextBuffer(AudioBufferProvider::Buffer* buffer);
+    // releaseBuffer() not overridden
+
+    // ExtendedAudioBufferProvider interface
+    virtual size_t framesReady() const;
+    virtual int64_t framesReleased() const;
+    virtual void onTimestamp(const ExtendedTimestamp &timestamp);
+
+};  // end of Track
+
diff --git a/services/audioflinger/PatchPanel.cpp b/services/audioflinger/PatchPanel.cpp
index 591a49e..d7c0728 100644
--- a/services/audioflinger/PatchPanel.cpp
+++ b/services/audioflinger/PatchPanel.cpp
@@ -274,13 +274,14 @@
                     audio_devices_t device = patch->sinks[0].ext.device.type;
                     String8 address = String8(patch->sinks[0].ext.device.address);
                     audio_io_handle_t output = AUDIO_IO_HANDLE_NONE;
-                    newPatch->mPlaybackThread = audioflinger->openOutput_l(
-                                                             patch->sinks[0].ext.device.hw_module,
-                                                             &output,
-                                                             &config,
-                                                             device,
-                                                             address,
-                                                             AUDIO_OUTPUT_FLAG_NONE);
+                    sp<ThreadBase> thread = audioflinger->openOutput_l(
+                                                            patch->sinks[0].ext.device.hw_module,
+                                                            &output,
+                                                            &config,
+                                                            device,
+                                                            address,
+                                                            AUDIO_OUTPUT_FLAG_NONE);
+                    newPatch->mPlaybackThread = (PlaybackThread *)thread.get();
                     ALOGV("audioflinger->openOutput_l() returned %p",
                                           newPatch->mPlaybackThread.get());
                     if (newPatch->mPlaybackThread == 0) {
@@ -310,13 +311,14 @@
                     config.format = newPatch->mPlaybackThread->format();
                 }
                 audio_io_handle_t input = AUDIO_IO_HANDLE_NONE;
-                newPatch->mRecordThread = audioflinger->openInput_l(srcModule,
+                sp<ThreadBase> thread = audioflinger->openInput_l(srcModule,
                                                                     &input,
                                                                     &config,
                                                                     device,
                                                                     address,
                                                                     AUDIO_SOURCE_MIC,
                                                                     AUDIO_INPUT_FLAG_NONE);
+                newPatch->mRecordThread = (RecordThread *)thread.get();
                 ALOGV("audioflinger->openInput_l() returned %p inChannelMask %08x",
                       newPatch->mRecordThread.get(), config.channel_mask);
                 if (newPatch->mRecordThread == 0) {
@@ -332,10 +334,13 @@
                     sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
                                                               patch->sinks[0].ext.mix.handle);
                     if (thread == 0) {
-                        ALOGW("createAudioPatch() bad capture I/O handle %d",
-                                                              patch->sinks[0].ext.mix.handle);
-                        status = BAD_VALUE;
-                        goto exit;
+                        thread = audioflinger->checkMmapThread_l(patch->sinks[0].ext.mix.handle);
+                        if (thread == 0) {
+                            ALOGW("createAudioPatch() bad capture I/O handle %d",
+                                                                  patch->sinks[0].ext.mix.handle);
+                            status = BAD_VALUE;
+                            goto exit;
+                        }
                     }
                     status = thread->sendCreateAudioPatchConfigEvent(patch, &halHandle);
                 } else {
@@ -376,10 +381,13 @@
             sp<ThreadBase> thread =
                             audioflinger->checkPlaybackThread_l(patch->sources[0].ext.mix.handle);
             if (thread == 0) {
-                ALOGW("createAudioPatch() bad playback I/O handle %d",
-                          patch->sources[0].ext.mix.handle);
-                status = BAD_VALUE;
-                goto exit;
+                thread = audioflinger->checkMmapThread_l(patch->sources[0].ext.mix.handle);
+                if (thread == 0) {
+                    ALOGW("createAudioPatch() bad playback I/O handle %d",
+                              patch->sources[0].ext.mix.handle);
+                    status = BAD_VALUE;
+                    goto exit;
+                }
             }
             if (thread == audioflinger->primaryPlaybackThread_l()) {
                 AudioParameter param = AudioParameter();
@@ -606,10 +614,13 @@
                 sp<ThreadBase> thread = audioflinger->checkRecordThread_l(
                                                                 patch->sinks[0].ext.mix.handle);
                 if (thread == 0) {
-                    ALOGW("releaseAudioPatch() bad capture I/O handle %d",
-                                                              patch->sinks[0].ext.mix.handle);
-                    status = BAD_VALUE;
-                    break;
+                    thread = audioflinger->checkMmapThread_l(patch->sinks[0].ext.mix.handle);
+                    if (thread == 0) {
+                        ALOGW("releaseAudioPatch() bad capture I/O handle %d",
+                                                                  patch->sinks[0].ext.mix.handle);
+                        status = BAD_VALUE;
+                        break;
+                    }
                 }
                 status = thread->sendReleaseAudioPatchConfigEvent(removedPatch->mHalHandle);
             } else {
@@ -629,10 +640,13 @@
             sp<ThreadBase> thread =
                             audioflinger->checkPlaybackThread_l(patch->sources[0].ext.mix.handle);
             if (thread == 0) {
-                ALOGW("releaseAudioPatch() bad playback I/O handle %d",
-                                                              patch->sources[0].ext.mix.handle);
-                status = BAD_VALUE;
-                break;
+                thread = audioflinger->checkMmapThread_l(patch->sources[0].ext.mix.handle);
+                if (thread == 0) {
+                    ALOGW("releaseAudioPatch() bad playback I/O handle %d",
+                                                                  patch->sources[0].ext.mix.handle);
+                    status = BAD_VALUE;
+                    break;
+                }
             }
             status = thread->sendReleaseAudioPatchConfigEvent(removedPatch->mHalHandle);
         } break;
diff --git a/services/audioflinger/PlaybackTracks.h b/services/audioflinger/PlaybackTracks.h
index 27e4627..72a3777 100644
--- a/services/audioflinger/PlaybackTracks.h
+++ b/services/audioflinger/PlaybackTracks.h
@@ -118,10 +118,9 @@
 
 public:
     void triggerEvents(AudioSystem::sync_event_t type);
-    void invalidate();
+    virtual void invalidate();
     void disable();
 
-    bool isInvalid() const { return mIsInvalid; }
     int fastIndex() const { return mFastIndex; }
 
 protected:
@@ -166,7 +165,6 @@
     volatile float      mCachedVolume;  // combined master volume and stream type volume;
                                         // 'volatile' means accessed without lock or
                                         // barrier, but is read/written atomically
-    bool                mIsInvalid; // non-resettable latch, set by invalidate()
     sp<AudioTrackServerProxy>  mAudioTrackServerProxy;
     bool                mResumeToStopping; // track was paused in stopping state.
     bool                mFlushHwPending; // track requests for thread flush
diff --git a/services/audioflinger/RecordTracks.h b/services/audioflinger/RecordTracks.h
index 848e531..72ebc93 100644
--- a/services/audioflinger/RecordTracks.h
+++ b/services/audioflinger/RecordTracks.h
@@ -42,7 +42,7 @@
 
             void        destroy();
 
-            void        invalidate();
+    virtual void        invalidate();
             // clear the buffer overflow flag
             void        clearOverflow() { mOverflow = false; }
             // set the buffer overflow flag and return previous value
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 724ce3c..c779716 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -508,7 +508,8 @@
         mAudioSource(AUDIO_SOURCE_DEFAULT), mId(id),
         // mName will be set by concrete (non-virtual) subclass
         mDeathRecipient(new PMDeathRecipient(this)),
-        mSystemReady(systemReady)
+        mSystemReady(systemReady),
+        mSignalPending(false)
 {
     memset(&mPatch, 0, sizeof(struct audio_patch));
 }
@@ -868,6 +869,8 @@
         return String16("AudioIn");
     case OFFLOAD:
         return String16("AudioOffload");
+    case MMAP:
+        return String16("Mmap");
     default:
         ALOG_ASSERT(false);
         return String16("AudioUnknown");
@@ -1590,6 +1593,16 @@
     }
 }
 
+void AudioFlinger::ThreadBase::broadcast_l()
+{
+    // Thread could be blocked waiting for async
+    // so signal it to handle state changes immediately
+    // If threadLoop is currently unlocked a signal of mWaitWorkCV will
+    // be lost so we also flag to prevent it blocking on mWaitWorkCV
+    mSignalPending = true;
+    mWaitWorkCV.broadcast();
+}
+
 // ----------------------------------------------------------------------------
 //      Playback
 // ----------------------------------------------------------------------------
@@ -1626,7 +1639,6 @@
         mUseAsyncWrite(false),
         mWriteAckSequence(0),
         mDrainSequence(0),
-        mSignalPending(false),
         mScreenState(AudioFlinger::mScreenState),
         // index 0 is reserved for normal mixer's submix
         mFastTrackAvailMask(((1 << FastMixerState::sMaxFastTracks) - 1) & ~1),
@@ -2057,6 +2069,9 @@
 
 void AudioFlinger::PlaybackThread::setMasterMute(bool muted)
 {
+    if (isDuplicating()) {
+        return;
+    }
     Mutex::Autolock _l(mLock);
     // Don't apply master mute in SW if our HAL can do it for us.
     if (mOutput && mOutput->audioHwDev &&
@@ -2198,16 +2213,6 @@
     }
 }
 
-void AudioFlinger::PlaybackThread::broadcast_l()
-{
-    // Thread could be blocked waiting for async
-    // so signal it to handle state changes immediately
-    // If threadLoop is currently unlocked a signal of mWaitWorkCV will
-    // be lost so we also flag to prevent it blocking on mWaitWorkCV
-    mSignalPending = true;
-    mWaitWorkCV.broadcast();
-}
-
 String8 AudioFlinger::PlaybackThread::getParameters(const String8& keys)
 {
     Mutex::Autolock _l(mLock);
@@ -5784,6 +5789,7 @@
     MixerThread::cacheParameters_l();
 }
 
+
 // ----------------------------------------------------------------------------
 //      Record
 // ----------------------------------------------------------------------------
@@ -7576,4 +7582,889 @@
     config->ext.mix.usecase.source = mAudioSource;
 }
 
+// ----------------------------------------------------------------------------
+//      Mmap
+// ----------------------------------------------------------------------------
+
+AudioFlinger::MmapThreadHandle::MmapThreadHandle(const sp<MmapThread>& thread)
+    : mThread(thread)
+{
+}
+
+AudioFlinger::MmapThreadHandle::~MmapThreadHandle()
+{
+    MmapThread *thread = mThread.get();
+    // clear our strong reference before disconnecting the thread: the last strong reference
+    // will be removed when closeInput/closeOutput is executed upono call from audio policy manager
+    // and the thread removed from mMMapThreads list causing the thread destruction.
+    mThread.clear();
+    if (thread != nullptr) {
+        thread->disconnect();
+    }
+}
+
+status_t AudioFlinger::MmapThreadHandle::createMmapBuffer(int32_t minSizeFrames,
+                                  struct audio_mmap_buffer_info *info)
+{
+    if (mThread == 0) {
+        return NO_INIT;
+    }
+    return mThread->createMmapBuffer(minSizeFrames, info);
+}
+
+status_t AudioFlinger::MmapThreadHandle::getMmapPosition(struct audio_mmap_position *position)
+{
+    if (mThread == 0) {
+        return NO_INIT;
+    }
+    return mThread->getMmapPosition(position);
+}
+
+status_t AudioFlinger::MmapThreadHandle::start(const MmapStreamInterface::Client& client, audio_port_handle_t *handle)
+
+{
+    if (mThread == 0) {
+        return NO_INIT;
+    }
+    return mThread->start(client, handle);
+}
+
+status_t AudioFlinger::MmapThreadHandle::stop(audio_port_handle_t handle)
+{
+    if (mThread == 0) {
+        return NO_INIT;
+    }
+    return mThread->stop(handle);
+}
+
+
+AudioFlinger::MmapThread::MmapThread(
+        const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
+        AudioHwDevice *hwDev, sp<StreamHalInterface> stream,
+        audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady)
+    : ThreadBase(audioFlinger, id, outDevice, inDevice, MMAP, systemReady),
+      mHalStream(stream), mHalDevice(hwDev->hwDevice()), mAudioHwDev(hwDev)
+{
+    readHalParameters_l();
+}
+
+AudioFlinger::MmapThread::~MmapThread()
+{
+}
+
+void AudioFlinger::MmapThread::onFirstRef()
+{
+    run(mThreadName, ANDROID_PRIORITY_URGENT_AUDIO);
+}
+
+void AudioFlinger::MmapThread::disconnect()
+{
+    for (const sp<MmapTrack> &t : mActiveTracks) {
+        stop(t->portId());
+    }
+    // this will cause the destruction of this thread.
+    if (isOutput()) {
+        AudioSystem::releaseOutput(mId, streamType(), mSessionId);
+    } else {
+        AudioSystem::releaseInput(mId, mSessionId);
+    }
+}
+
+
+void AudioFlinger::MmapThread::configure(const audio_attributes_t *attr,
+                                                audio_stream_type_t streamType __unused,
+                                                audio_session_t sessionId,
+                                                const sp<MmapStreamCallback>& callback,
+                                                audio_port_handle_t portId)
+{
+    mAttr = *attr;
+    mSessionId = sessionId;
+    mCallback = callback;
+    mPortId = portId;
+}
+
+status_t AudioFlinger::MmapThread::createMmapBuffer(int32_t minSizeFrames,
+                                  struct audio_mmap_buffer_info *info)
+{
+    if (mHalStream == 0) {
+        return NO_INIT;
+    }
+    return mHalStream->createMmapBuffer(minSizeFrames, info);
+}
+
+status_t AudioFlinger::MmapThread::getMmapPosition(struct audio_mmap_position *position)
+{
+    if (mHalStream == 0) {
+        return NO_INIT;
+    }
+    return mHalStream->getMmapPosition(position);
+}
+
+status_t AudioFlinger::MmapThread::start(const MmapStreamInterface::Client& client,
+                                         audio_port_handle_t *handle)
+{
+    ALOGV("%s clientUid %d", __FUNCTION__, client.clientUid);
+    if (mHalStream == 0) {
+        return NO_INIT;
+    }
+
+    status_t ret;
+    audio_session_t sessionId;
+    audio_port_handle_t portId;
+
+    if (mActiveTracks.size() == 0) {
+        // for the first track, reuse portId and session allocated when the stream was opened
+        mHalStream->start();
+        portId = mPortId;
+        sessionId = mSessionId;
+    } else {
+        // for other tracks than first one, get a new port ID from APM.
+        sessionId = (audio_session_t)mAudioFlinger->newAudioUniqueId(AUDIO_UNIQUE_ID_USE_SESSION);
+        audio_io_handle_t io;
+        if (isOutput()) {
+            audio_config_t config = AUDIO_CONFIG_INITIALIZER;
+            config.sample_rate = mSampleRate;
+            config.channel_mask = mChannelMask;
+            config.format = mFormat;
+            audio_stream_type_t stream = streamType();
+            audio_output_flags_t flags =
+                    (audio_output_flags_t)(AUDIO_OUTPUT_FLAG_MMAP_NOIRQ | AUDIO_OUTPUT_FLAG_DIRECT);
+            ret = AudioSystem::getOutputForAttr(&mAttr, &io,
+                                                sessionId,
+                                                &stream,
+                                                client.clientUid,
+                                                &config,
+                                                flags,
+                                                AUDIO_PORT_HANDLE_NONE,
+                                                &portId);
+        } else {
+            audio_config_base_t config;
+            config.sample_rate = mSampleRate;
+            config.channel_mask = mChannelMask;
+            config.format = mFormat;
+            ret = AudioSystem::getInputForAttr(&mAttr, &io,
+                                                  sessionId,
+                                                  client.clientPid,
+                                                  client.clientUid,
+                                                  &config,
+                                                  AUDIO_INPUT_FLAG_MMAP_NOIRQ,
+                                                  AUDIO_PORT_HANDLE_NONE,
+                                                  &portId);
+        }
+        // APM should not chose a different input or output stream for the same set of attributes
+        // and audo configuration
+        if (ret != NO_ERROR || io != mId) {
+            ALOGE("%s: error getting output or input from APM (error %d, io %d expected io %d)",
+                  __FUNCTION__, ret, io, mId);
+            return BAD_VALUE;
+        }
+    }
+
+    if (isOutput()) {
+        ret = AudioSystem::startOutput(mId, streamType(), sessionId);
+    } else {
+        ret = AudioSystem::startInput(mId, sessionId);
+    }
+
+    // abort if start is rejected by audio policy manager
+    if (ret != NO_ERROR) {
+        if (mActiveTracks.size() != 0) {
+            if (isOutput()) {
+                AudioSystem::releaseOutput(mId, streamType(), sessionId);
+            } else {
+                AudioSystem::releaseInput(mId, sessionId);
+            }
+        }
+        return PERMISSION_DENIED;
+    }
+
+    sp<MmapTrack> track = new MmapTrack(this, mSampleRate, mFormat, mChannelMask, sessionId,
+                                        client.clientUid, portId);
+
+    mActiveTracks.add(track);
+    sp<EffectChain> chain = getEffectChain_l(sessionId);
+    if (chain != 0) {
+        chain->setStrategy(AudioSystem::getStrategyForStream(streamType()));
+        chain->incTrackCnt();
+        chain->incActiveTrackCnt();
+    }
+
+    *handle = portId;
+
+    broadcast_l();
+
+    ALOGV("%s DONE handle %d", __FUNCTION__, portId);
+
+    return NO_ERROR;
+}
+
+status_t AudioFlinger::MmapThread::stop(audio_port_handle_t handle)
+{
+
+    ALOGV("%s handle %d", __FUNCTION__, handle);
+
+    if (mHalStream == 0) {
+        return NO_INIT;
+    }
+
+    sp<MmapTrack> track;
+    for (const sp<MmapTrack> &t : mActiveTracks) {
+        if (handle == t->portId()) {
+            track = t;
+            break;
+        }
+    }
+    if (track == 0) {
+        return BAD_VALUE;
+    }
+
+    mActiveTracks.remove(track);
+
+    if (isOutput()) {
+        AudioSystem::stopOutput(mId, streamType(), track->sessionId());
+        if (mActiveTracks.size() != 0) {
+            AudioSystem::releaseOutput(mId, streamType(), track->sessionId());
+        }
+    } else {
+        AudioSystem::stopInput(mId, track->sessionId());
+        if (mActiveTracks.size() != 0) {
+            AudioSystem::releaseInput(mId, track->sessionId());
+        }
+    }
+
+    sp<EffectChain> chain = getEffectChain_l(track->sessionId());
+    if (chain != 0) {
+        chain->decActiveTrackCnt();
+        chain->decTrackCnt();
+    }
+
+    broadcast_l();
+
+    if (mActiveTracks.size() == 0) {
+        mHalStream->stop();
+    }
+    return NO_ERROR;
+}
+
+
+void AudioFlinger::MmapThread::readHalParameters_l()
+{
+    status_t result = mHalStream->getAudioProperties(&mSampleRate, &mChannelMask, &mHALFormat);
+    LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving audio properties from HAL: %d", result);
+    mFormat = mHALFormat;
+    LOG_ALWAYS_FATAL_IF(!audio_is_linear_pcm(mFormat), "HAL format %#x is not linear pcm", mFormat);
+    result = mHalStream->getFrameSize(&mFrameSize);
+    LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving frame size from HAL: %d", result);
+    result = mHalStream->getBufferSize(&mBufferSize);
+    LOG_ALWAYS_FATAL_IF(result != OK, "Error retrieving buffer size from HAL: %d", result);
+    mFrameCount = mBufferSize / mFrameSize;
+}
+
+bool AudioFlinger::MmapThread::threadLoop()
+{
+    acquireWakeLock();
+
+    checkSilentMode_l();
+
+    const String8 myName(String8::format("thread %p type %d TID %d", this, mType, gettid()));
+
+    while (!exitPending())
+    {
+        Mutex::Autolock _l(mLock);
+        Vector< sp<EffectChain> > effectChains;
+
+        if (mSignalPending) {
+            // A signal was raised while we were unlocked
+            mSignalPending = false;
+        } else {
+            if (mConfigEvents.isEmpty()) {
+                // we're about to wait, flush the binder command buffer
+                IPCThreadState::self()->flushCommands();
+
+                if (exitPending()) {
+                    break;
+                }
+
+                bool wakelockReleased = false;
+                if (mActiveTracks.size() == 0) {
+                    releaseWakeLock_l();
+                    wakelockReleased = true;
+                }
+                // wait until we have something to do...
+                ALOGV("%s going to sleep", myName.string());
+                mWaitWorkCV.wait(mLock);
+                ALOGV("%s waking up", myName.string());
+                if (wakelockReleased) {
+                    acquireWakeLock_l();
+                }
+
+                checkSilentMode_l();
+
+                continue;
+            }
+        }
+
+        processConfigEvents_l();
+
+        processVolume_l();
+
+        checkInvalidTracks_l();
+
+        mActiveTracks.updatePowerState(this);
+
+        lockEffectChains_l(effectChains);
+        for (size_t i = 0; i < effectChains.size(); i ++) {
+            effectChains[i]->process_l();
+        }
+        // enable changes in effect chain
+        unlockEffectChains(effectChains);
+        // Effect chains will be actually deleted here if they were removed from
+        // mEffectChains list during mixing or effects processing
+    }
+
+    threadLoop_exit();
+
+    if (!mStandby) {
+        threadLoop_standby();
+        mStandby = true;
+    }
+
+    releaseWakeLock();
+
+    ALOGV("Thread %p type %d exiting", this, mType);
+    return false;
+}
+
+// checkForNewParameter_l() must be called with ThreadBase::mLock held
+bool AudioFlinger::MmapThread::checkForNewParameter_l(const String8& keyValuePair,
+                                                              status_t& status)
+{
+    AudioParameter param = AudioParameter(keyValuePair);
+    int value;
+    if (param.getInt(String8(AudioParameter::keyRouting), value) == NO_ERROR) {
+        // forward device change to effects that have requested to be
+        // aware of attached audio device.
+        if (value != AUDIO_DEVICE_NONE) {
+            mOutDevice = value;
+            for (size_t i = 0; i < mEffectChains.size(); i++) {
+                mEffectChains[i]->setDevice_l(mOutDevice);
+            }
+        }
+    }
+    status = mHalStream->setParameters(keyValuePair);
+
+    return false;
+}
+
+String8 AudioFlinger::MmapThread::getParameters(const String8& keys)
+{
+    Mutex::Autolock _l(mLock);
+    String8 out_s8;
+    if (initCheck() == NO_ERROR && mHalStream->getParameters(keys, &out_s8) == OK) {
+        return out_s8;
+    }
+    return String8();
+}
+
+void AudioFlinger::MmapThread::ioConfigChanged(audio_io_config_event event, pid_t pid) {
+    sp<AudioIoDescriptor> desc = new AudioIoDescriptor();
+
+    desc->mIoHandle = mId;
+
+    switch (event) {
+    case AUDIO_INPUT_OPENED:
+    case AUDIO_INPUT_CONFIG_CHANGED:
+    case AUDIO_OUTPUT_OPENED:
+    case AUDIO_OUTPUT_CONFIG_CHANGED:
+        desc->mPatch = mPatch;
+        desc->mChannelMask = mChannelMask;
+        desc->mSamplingRate = mSampleRate;
+        desc->mFormat = mFormat;
+        desc->mFrameCount = mFrameCount;
+        desc->mFrameCountHAL = mFrameCount;
+        desc->mLatency = 0;
+        break;
+
+    case AUDIO_INPUT_CLOSED:
+    case AUDIO_OUTPUT_CLOSED:
+    default:
+        break;
+    }
+    mAudioFlinger->ioConfigChanged(event, desc, pid);
+}
+
+status_t AudioFlinger::MmapThread::createAudioPatch_l(const struct audio_patch *patch,
+                                                          audio_patch_handle_t *handle)
+{
+    status_t status = NO_ERROR;
+
+    // store new device and send to effects
+    audio_devices_t type = AUDIO_DEVICE_NONE;
+    audio_port_handle_t deviceId;
+    if (isOutput()) {
+        for (unsigned int i = 0; i < patch->num_sinks; i++) {
+            type |= patch->sinks[i].ext.device.type;
+        }
+        deviceId = patch->sinks[0].id;
+    } else {
+        type = patch->sources[0].ext.device.type;
+        deviceId = patch->sources[0].id;
+    }
+
+    for (size_t i = 0; i < mEffectChains.size(); i++) {
+        mEffectChains[i]->setDevice_l(type);
+    }
+
+    if (isOutput()) {
+        mOutDevice = type;
+    } else {
+        mInDevice = type;
+        // store new source and send to effects
+        if (mAudioSource != patch->sinks[0].ext.mix.usecase.source) {
+            mAudioSource = patch->sinks[0].ext.mix.usecase.source;
+            for (size_t i = 0; i < mEffectChains.size(); i++) {
+                mEffectChains[i]->setAudioSource_l(mAudioSource);
+            }
+        }
+    }
+
+    if (mAudioHwDev->supportsAudioPatches()) {
+        status = mHalDevice->createAudioPatch(patch->num_sources,
+                                            patch->sources,
+                                            patch->num_sinks,
+                                            patch->sinks,
+                                            handle);
+    } else {
+        char *address;
+        if (strcmp(patch->sinks[0].ext.device.address, "") != 0) {
+            //FIXME: we only support address on first sink with HAL version < 3.0
+            address = audio_device_address_to_parameter(
+                                                        patch->sinks[0].ext.device.type,
+                                                        patch->sinks[0].ext.device.address);
+        } else {
+            address = (char *)calloc(1, 1);
+        }
+        AudioParameter param = AudioParameter(String8(address));
+        free(address);
+        param.addInt(String8(AudioParameter::keyRouting), (int)type);
+        if (!isOutput()) {
+            param.addInt(String8(AudioParameter::keyInputSource),
+                                         (int)patch->sinks[0].ext.mix.usecase.source);
+        }
+        status = mHalStream->setParameters(param.toString());
+        *handle = AUDIO_PATCH_HANDLE_NONE;
+    }
+
+    if (isOutput() && mPrevOutDevice != mOutDevice) {
+        mPrevOutDevice = type;
+        sendIoConfigEvent_l(AUDIO_OUTPUT_CONFIG_CHANGED);
+        if (mCallback != 0) {
+            mCallback->onRoutingChanged(deviceId);
+        }
+    }
+    if (!isOutput() && mPrevInDevice != mInDevice) {
+        mPrevInDevice = type;
+        sendIoConfigEvent_l(AUDIO_INPUT_CONFIG_CHANGED);
+        if (mCallback != 0) {
+            mCallback->onRoutingChanged(deviceId);
+        }
+    }
+    return status;
+}
+
+status_t AudioFlinger::MmapThread::releaseAudioPatch_l(const audio_patch_handle_t handle)
+{
+    status_t status = NO_ERROR;
+
+    mInDevice = AUDIO_DEVICE_NONE;
+
+    bool supportsAudioPatches = mHalDevice->supportsAudioPatches(&supportsAudioPatches) == OK ?
+                                        supportsAudioPatches : false;
+
+    if (supportsAudioPatches) {
+        status = mHalDevice->releaseAudioPatch(handle);
+    } else {
+        AudioParameter param;
+        param.addInt(String8(AudioParameter::keyRouting), 0);
+        status = mHalStream->setParameters(param.toString());
+    }
+    return status;
+}
+
+void AudioFlinger::MmapThread::getAudioPortConfig(struct audio_port_config *config)
+{
+    ThreadBase::getAudioPortConfig(config);
+    if (isOutput()) {
+        config->role = AUDIO_PORT_ROLE_SOURCE;
+        config->ext.mix.hw_module = mAudioHwDev->handle();
+        config->ext.mix.usecase.stream = AUDIO_STREAM_DEFAULT;
+    } else {
+        config->role = AUDIO_PORT_ROLE_SINK;
+        config->ext.mix.hw_module = mAudioHwDev->handle();
+        config->ext.mix.usecase.source = mAudioSource;
+    }
+}
+
+status_t AudioFlinger::MmapThread::addEffectChain_l(const sp<EffectChain>& chain)
+{
+    audio_session_t session = chain->sessionId();
+
+    ALOGV("addEffectChain_l() %p on thread %p for session %d", chain.get(), this, session);
+    // Attach all tracks with same session ID to this chain.
+    // indicate all active tracks in the chain
+    for (const sp<MmapTrack> &track : mActiveTracks) {
+        if (session == track->sessionId()) {
+            chain->incTrackCnt();
+            chain->incActiveTrackCnt();
+        }
+    }
+
+    chain->setThread(this);
+    chain->setInBuffer(nullptr);
+    chain->setOutBuffer(nullptr);
+    chain->syncHalEffectsState();
+
+    mEffectChains.add(chain);
+    checkSuspendOnAddEffectChain_l(chain);
+    return NO_ERROR;
+}
+
+size_t AudioFlinger::MmapThread::removeEffectChain_l(const sp<EffectChain>& chain)
+{
+    audio_session_t session = chain->sessionId();
+
+    ALOGV("removeEffectChain_l() %p from thread %p for session %d", chain.get(), this, session);
+
+    for (size_t i = 0; i < mEffectChains.size(); i++) {
+        if (chain == mEffectChains[i]) {
+            mEffectChains.removeAt(i);
+            // detach all active tracks from the chain
+            // detach all tracks with same session ID from this chain
+            for (const sp<MmapTrack> &track : mActiveTracks) {
+                if (session == track->sessionId()) {
+                    chain->decActiveTrackCnt();
+                    chain->decTrackCnt();
+                }
+            }
+            break;
+        }
+    }
+    return mEffectChains.size();
+}
+
+// hasAudioSession_l() must be called with ThreadBase::mLock held
+uint32_t AudioFlinger::MmapThread::hasAudioSession_l(audio_session_t sessionId) const
+{
+    uint32_t result = 0;
+    if (getEffectChain_l(sessionId) != 0) {
+        result = EFFECT_SESSION;
+    }
+
+    for (size_t i = 0; i < mActiveTracks.size(); i++) {
+        sp<MmapTrack> track = mActiveTracks[i];
+        if (sessionId == track->sessionId()) {
+            result |= TRACK_SESSION;
+            if (track->isFastTrack()) {
+                result |= FAST_SESSION;
+            }
+            break;
+        }
+    }
+
+    return result;
+}
+
+void AudioFlinger::MmapThread::threadLoop_standby()
+{
+    mHalStream->standby();
+}
+
+void AudioFlinger::MmapThread::threadLoop_exit()
+{
+    if (mCallback != 0) {
+        mCallback->onTearDown();
+    }
+}
+
+status_t AudioFlinger::MmapThread::setSyncEvent(const sp<SyncEvent>& event __unused)
+{
+    return BAD_VALUE;
+}
+
+bool AudioFlinger::MmapThread::isValidSyncEvent(const sp<SyncEvent>& event __unused) const
+{
+    return false;
+}
+
+status_t AudioFlinger::MmapThread::checkEffectCompatibility_l(
+        const effect_descriptor_t *desc, audio_session_t sessionId)
+{
+    // No global effect sessions on mmap threads
+    if (sessionId == AUDIO_SESSION_OUTPUT_MIX || sessionId == AUDIO_SESSION_OUTPUT_STAGE) {
+        ALOGW("checkEffectCompatibility_l(): global effect %s on record thread %s",
+                desc->name, mThreadName);
+        return BAD_VALUE;
+    }
+
+    if (!isOutput() && ((desc->flags & EFFECT_FLAG_TYPE_MASK) != EFFECT_FLAG_TYPE_PRE_PROC)) {
+        ALOGW("checkEffectCompatibility_l(): non pre processing effect %s on capture mmap thread",
+                desc->name);
+        return BAD_VALUE;
+    }
+    if (isOutput() && ((desc->flags & EFFECT_FLAG_TYPE_MASK) == EFFECT_FLAG_TYPE_PRE_PROC)) {
+        ALOGW("checkEffectCompatibility_l(): pre processing effect %s created on playback mmap thread",
+              desc->name);
+        return BAD_VALUE;
+    }
+
+    // Only allow effects without processing load or latency
+    if ((desc->flags & EFFECT_FLAG_NO_PROCESS_MASK) != EFFECT_FLAG_NO_PROCESS) {
+        return BAD_VALUE;
+    }
+
+    return NO_ERROR;
+
+}
+
+void AudioFlinger::MmapThread::checkInvalidTracks_l()
+{
+    for (const sp<MmapTrack> &track : mActiveTracks) {
+        if (track->isInvalid()) {
+            if (mCallback != 0) {
+                mCallback->onTearDown();
+            }
+            break;
+        }
+    }
+}
+
+void AudioFlinger::MmapThread::dump(int fd, const Vector<String16>& args)
+{
+    dumpInternals(fd, args);
+    dumpTracks(fd, args);
+    dumpEffectChains(fd, args);
+}
+
+void AudioFlinger::MmapThread::dumpInternals(int fd, const Vector<String16>& args)
+{
+    dprintf(fd, "\nMmap thread %p:\n", this);
+
+    dumpBase(fd, args);
+
+    dprintf(fd, "  Attributes: content type %d usage %d source %d\n",
+            mAttr.content_type, mAttr.usage, mAttr.source);
+    dprintf(fd, "  Session: %d port Id: %d\n", mSessionId, mPortId);
+    if (mActiveTracks.size() == 0) {
+        dprintf(fd, "  No active clients\n");
+    }
+}
+
+void AudioFlinger::MmapThread::dumpTracks(int fd, const Vector<String16>& args __unused)
+{
+    const size_t SIZE = 256;
+    char buffer[SIZE];
+    String8 result;
+
+    size_t numtracks = mActiveTracks.size();
+    dprintf(fd, "  %zu Tracks", numtracks);
+    if (numtracks) {
+        MmapTrack::appendDumpHeader(result);
+        for (size_t i = 0; i < numtracks ; ++i) {
+            sp<MmapTrack> track = mActiveTracks[i];
+            track->dump(buffer, SIZE);
+            result.append(buffer);
+        }
+    } else {
+        dprintf(fd, "\n");
+    }
+    write(fd, result.string(), result.size());
+}
+
+AudioFlinger::MmapPlaybackThread::MmapPlaybackThread(
+        const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
+        AudioHwDevice *hwDev,  AudioStreamOut *output,
+        audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady)
+    : MmapThread(audioFlinger, id, hwDev, output->stream, outDevice, inDevice, systemReady),
+      mStreamType(AUDIO_STREAM_MUSIC),
+      mStreamVolume(1.0), mStreamMute(false), mOutput(output)
+{
+    snprintf(mThreadName, kThreadNameLength, "AudioMmapOut_%X", id);
+    mChannelCount = audio_channel_count_from_out_mask(mChannelMask);
+    mMasterVolume = audioFlinger->masterVolume_l();
+    mMasterMute = audioFlinger->masterMute_l();
+    if (mAudioHwDev) {
+        if (mAudioHwDev->canSetMasterVolume()) {
+            mMasterVolume = 1.0;
+        }
+
+        if (mAudioHwDev->canSetMasterMute()) {
+            mMasterMute = false;
+        }
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::configure(const audio_attributes_t *attr,
+                                                audio_stream_type_t streamType,
+                                                audio_session_t sessionId,
+                                                const sp<MmapStreamCallback>& callback,
+                                                audio_port_handle_t portId)
+{
+    MmapThread::configure(attr, streamType, sessionId, callback, portId);
+    mStreamType = streamType;
+}
+
+AudioStreamOut* AudioFlinger::MmapPlaybackThread::clearOutput()
+{
+    Mutex::Autolock _l(mLock);
+    AudioStreamOut *output = mOutput;
+    mOutput = NULL;
+    return output;
+}
+
+void AudioFlinger::MmapPlaybackThread::setMasterVolume(float value)
+{
+    Mutex::Autolock _l(mLock);
+    // Don't apply master volume in SW if our HAL can do it for us.
+    if (mAudioHwDev &&
+            mAudioHwDev->canSetMasterVolume()) {
+        mMasterVolume = 1.0;
+    } else {
+        mMasterVolume = value;
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::setMasterMute(bool muted)
+{
+    Mutex::Autolock _l(mLock);
+    // Don't apply master mute in SW if our HAL can do it for us.
+    if (mAudioHwDev && mAudioHwDev->canSetMasterMute()) {
+        mMasterMute = false;
+    } else {
+        mMasterMute = muted;
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::setStreamVolume(audio_stream_type_t stream, float value)
+{
+    Mutex::Autolock _l(mLock);
+    if (stream == mStreamType) {
+        mStreamVolume = value;
+        broadcast_l();
+    }
+}
+
+float AudioFlinger::MmapPlaybackThread::streamVolume(audio_stream_type_t stream) const
+{
+    Mutex::Autolock _l(mLock);
+    if (stream == mStreamType) {
+        return mStreamVolume;
+    }
+    return 0.0f;
+}
+
+void AudioFlinger::MmapPlaybackThread::setStreamMute(audio_stream_type_t stream, bool muted)
+{
+    Mutex::Autolock _l(mLock);
+    if (stream == mStreamType) {
+        mStreamMute= muted;
+        broadcast_l();
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::invalidateTracks(audio_stream_type_t streamType)
+{
+    Mutex::Autolock _l(mLock);
+    if (streamType == mStreamType) {
+        for (const sp<MmapTrack> &track : mActiveTracks) {
+            track->invalidate();
+        }
+        broadcast_l();
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::processVolume_l()
+{
+    float volume;
+
+    if (mMasterMute || mStreamMute) {
+        volume = 0;
+    } else {
+        volume = mMasterVolume * mStreamVolume;
+    }
+
+    if (volume != mHalVolFloat) {
+        mHalVolFloat = volume;
+
+        // Convert volumes from float to 8.24
+        uint32_t vol = (uint32_t)(volume * (1 << 24));
+
+        // Delegate volume control to effect in track effect chain if needed
+        // only one effect chain can be present on DirectOutputThread, so if
+        // there is one, the track is connected to it
+        if (!mEffectChains.isEmpty()) {
+            mEffectChains[0]->setVolume_l(&vol, &vol);
+            volume = (float)vol / (1 << 24);
+        }
+
+        mOutput->stream->setVolume(volume, volume);
+
+        if (mCallback != 0) {
+            int channelCount;
+            if (isOutput()) {
+                channelCount = audio_channel_count_from_out_mask(mChannelMask);
+            } else {
+                channelCount = audio_channel_count_from_in_mask(mChannelMask);
+            }
+            Vector<float> values;
+            for (int i = 0; i < channelCount; i++) {
+                values.add(volume);
+            }
+            mCallback->onVolumeChanged(mChannelMask, values);
+        }
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::checkSilentMode_l()
+{
+    if (!mMasterMute) {
+        char value[PROPERTY_VALUE_MAX];
+        if (property_get("ro.audio.silent", value, "0") > 0) {
+            char *endptr;
+            unsigned long ul = strtoul(value, &endptr, 0);
+            if (*endptr == '\0' && ul != 0) {
+                ALOGD("Silence is golden");
+                // The setprop command will not allow a property to be changed after
+                // the first time it is set, so we don't have to worry about un-muting.
+                setMasterMute_l(true);
+            }
+        }
+    }
+}
+
+void AudioFlinger::MmapPlaybackThread::dumpInternals(int fd, const Vector<String16>& args)
+{
+    MmapThread::dumpInternals(fd, args);
+
+    dprintf(fd, "  Stream type: %d Stream volume: %f HAL volume: %f Stream mute %d\n", mStreamType, mStreamVolume, mHalVolFloat, mStreamMute);
+    dprintf(fd, "  Master volume: %f Master mute %d\n", mMasterVolume, mMasterMute);
+}
+
+AudioFlinger::MmapCaptureThread::MmapCaptureThread(
+        const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
+        AudioHwDevice *hwDev,  AudioStreamIn *input,
+        audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady)
+    : MmapThread(audioFlinger, id, hwDev, input->stream, outDevice, inDevice, systemReady),
+      mInput(input)
+{
+    snprintf(mThreadName, kThreadNameLength, "AudioMmapIn_%X", id);
+    mChannelCount = audio_channel_count_from_in_mask(mChannelMask);
+}
+
+AudioFlinger::AudioStreamIn* AudioFlinger::MmapCaptureThread::clearInput()
+{
+    Mutex::Autolock _l(mLock);
+    AudioStreamIn *input = mInput;
+    mInput = NULL;
+    return input;
+}
 } // namespace android
diff --git a/services/audioflinger/Threads.h b/services/audioflinger/Threads.h
index e43f001..4d1b129 100644
--- a/services/audioflinger/Threads.h
+++ b/services/audioflinger/Threads.h
@@ -29,7 +29,8 @@
         DIRECT,             // Thread class is DirectOutputThread
         DUPLICATING,        // Thread class is DuplicatingThread
         RECORD,             // Thread class is RecordThread
-        OFFLOAD             // Thread class is OffloadThread
+        OFFLOAD,            // Thread class is OffloadThread
+        MMAP                // control thread for MMAP stream
     };
 
     static const char *threadTypeToString(type_t type);
@@ -390,6 +391,8 @@
                 virtual status_t    checkEffectCompatibility_l(const effect_descriptor_t *desc,
                                                                audio_session_t sessionId) = 0;
 
+                        void        broadcast_l();
+
     mutable     Mutex                   mLock;
 
 protected:
@@ -482,6 +485,9 @@
                 sp<NBLog::Writer>       mNBLogWriter;
                 bool                    mSystemReady;
                 ExtendedTimestamp       mTimestamp;
+                // A condition that must be evaluated by the thread loop has changed and
+                // we must not wait for async write callback in the thread loop before evaluating it
+                bool                    mSignalPending;
 
                 // ActiveTracks is a sorted vector of track type T representing the
                 // active tracks of threadLoop() to be considered by the locked prepare portion.
@@ -569,8 +575,22 @@
                 };
 };
 
+class VolumeInterface {
+ public:
+
+    virtual ~VolumeInterface() {}
+
+    virtual void        setMasterVolume(float value) = 0;
+    virtual void        setMasterMute(bool muted) = 0;
+    virtual void        setStreamVolume(audio_stream_type_t stream, float value) = 0;
+    virtual void        setStreamMute(audio_stream_type_t stream, bool muted) = 0;
+    virtual float       streamVolume(audio_stream_type_t stream) const = 0;
+
+};
+
 // --- PlaybackThread ---
-class PlaybackThread : public ThreadBase, public StreamOutHalInterfaceCallback {
+class PlaybackThread : public ThreadBase, public StreamOutHalInterfaceCallback,
+    public VolumeInterface {
 public:
 
 #include "PlaybackTracks.h"
@@ -659,13 +679,12 @@
                 // same, but lock must already be held
                 uint32_t    latency_l() const;
 
-                void        setMasterVolume(float value);
-                void        setMasterMute(bool muted);
-
-                void        setStreamVolume(audio_stream_type_t stream, float value);
-                void        setStreamMute(audio_stream_type_t stream, bool muted);
-
-                float       streamVolume(audio_stream_type_t stream) const;
+                // VolumeInterface
+    virtual     void        setMasterVolume(float value);
+    virtual     void        setMasterMute(bool muted);
+    virtual     void        setStreamVolume(audio_stream_type_t stream, float value);
+    virtual     void        setStreamMute(audio_stream_type_t stream, bool muted);
+    virtual     float       streamVolume(audio_stream_type_t stream) const;
 
                 sp<Track>   createTrack_l(
                                 const sp<AudioFlinger::Client>& client,
@@ -868,7 +887,6 @@
     status_t    addTrack_l(const sp<Track>& track);
     bool        destroyTrack_l(const sp<Track>& track);
     void        removeTrack_l(const sp<Track>& track);
-    void        broadcast_l();
 
     void        readOutputParameters_l();
 
@@ -930,9 +948,6 @@
     // Bit 0 is reset by the async callback thread calling resetDraining(). Out of sequence
     // callbacks are ignored.
     uint32_t                        mDrainSequence;
-    // A condition that must be evaluated by prepareTrack_l() has changed and we must not wait
-    // for async write callback in the thread loop before evaluating it
-    bool                            mSignalPending;
     sp<AsyncCallbackThread>         mCallbackThread;
 
 private:
@@ -1274,7 +1289,6 @@
     virtual     bool        hasFastMixer() const { return false; }
 };
 
-
 // record thread
 class RecordThread : public ThreadBase
 {
@@ -1577,3 +1591,153 @@
 
             bool                                mFastTrackAvail;    // true if fast track available
 };
+
+class MmapThread : public ThreadBase
+{
+ public:
+
+#include "MmapTracks.h"
+
+    MmapThread(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
+                      AudioHwDevice *hwDev, sp<StreamHalInterface> stream,
+                      audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady);
+    virtual     ~MmapThread();
+
+    virtual     void        configure(const audio_attributes_t *attr,
+                                      audio_stream_type_t streamType,
+                                      audio_session_t sessionId,
+                                      const sp<MmapStreamCallback>& callback,
+                                      audio_port_handle_t portId);
+
+                void        disconnect();
+
+    // MmapStreamInterface
+    status_t createMmapBuffer(int32_t minSizeFrames,
+                                      struct audio_mmap_buffer_info *info);
+    status_t getMmapPosition(struct audio_mmap_position *position);
+    status_t start(const MmapStreamInterface::Client& client, audio_port_handle_t *handle);
+    status_t stop(audio_port_handle_t handle);
+
+    // RefBase
+    virtual     void        onFirstRef();
+
+    // Thread virtuals
+    virtual     bool        threadLoop();
+
+    virtual     void        threadLoop_exit();
+    virtual     void        threadLoop_standby();
+
+    virtual     status_t    initCheck() const { return (mHalStream == 0) ? NO_INIT : NO_ERROR; }
+    virtual     size_t      frameCount() const { return mFrameCount; }
+    virtual     bool        checkForNewParameter_l(const String8& keyValuePair,
+                                                    status_t& status);
+    virtual     String8     getParameters(const String8& keys);
+    virtual     void        ioConfigChanged(audio_io_config_event event, pid_t pid = 0);
+                void        readHalParameters_l();
+    virtual     void        cacheParameters_l() {}
+    virtual     status_t    createAudioPatch_l(const struct audio_patch *patch,
+                                               audio_patch_handle_t *handle);
+    virtual     status_t    releaseAudioPatch_l(const audio_patch_handle_t handle);
+    virtual     void        getAudioPortConfig(struct audio_port_config *config);
+
+    virtual     sp<StreamHalInterface> stream() const { return mHalStream; }
+    virtual     status_t    addEffectChain_l(const sp<EffectChain>& chain);
+    virtual     size_t      removeEffectChain_l(const sp<EffectChain>& chain);
+    virtual     status_t    checkEffectCompatibility_l(const effect_descriptor_t *desc,
+                                                               audio_session_t sessionId);
+
+    virtual     uint32_t    hasAudioSession_l(audio_session_t sessionId) const;
+    virtual     status_t    setSyncEvent(const sp<SyncEvent>& event);
+    virtual     bool        isValidSyncEvent(const sp<SyncEvent>& event) const;
+
+    virtual     void        checkSilentMode_l() {}
+    virtual     void        processVolume_l() {}
+                void        checkInvalidTracks_l();
+
+    virtual     audio_stream_type_t streamType() { return AUDIO_STREAM_DEFAULT; }
+
+    virtual     void        invalidateTracks(audio_stream_type_t streamType __unused) {}
+
+                void        dump(int fd, const Vector<String16>& args);
+    virtual     void        dumpInternals(int fd, const Vector<String16>& args);
+                void        dumpTracks(int fd, const Vector<String16>& args);
+
+    virtual     bool        isOutput() const = 0;
+
+ protected:
+
+                audio_attributes_t      mAttr;
+                audio_session_t         mSessionId;
+                audio_port_handle_t     mPortId;
+
+                sp<MmapStreamCallback>  mCallback;
+                sp<StreamHalInterface>  mHalStream;
+                sp<DeviceHalInterface>  mHalDevice;
+                AudioHwDevice* const    mAudioHwDev;
+                ActiveTracks<MmapTrack> mActiveTracks;
+};
+
+class MmapPlaybackThread : public MmapThread, public VolumeInterface
+{
+
+public:
+    MmapPlaybackThread(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
+                      AudioHwDevice *hwDev, AudioStreamOut *output,
+                      audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady);
+    virtual     ~MmapPlaybackThread() {}
+
+    virtual     void        configure(const audio_attributes_t *attr,
+                                      audio_stream_type_t streamType,
+                                      audio_session_t sessionId,
+                                      const sp<MmapStreamCallback>& callback,
+                                      audio_port_handle_t portId);
+
+                AudioStreamOut* clearOutput();
+
+                // VolumeInterface
+    virtual     void        setMasterVolume(float value);
+    virtual     void        setMasterMute(bool muted);
+    virtual     void        setStreamVolume(audio_stream_type_t stream, float value);
+    virtual     void        setStreamMute(audio_stream_type_t stream, bool muted);
+    virtual     float       streamVolume(audio_stream_type_t stream) const;
+
+                void        setMasterMute_l(bool muted) { mMasterMute = muted; }
+
+    virtual     void        invalidateTracks(audio_stream_type_t streamType);
+
+    virtual     audio_stream_type_t streamType() { return mStreamType; }
+    virtual     void        checkSilentMode_l();
+    virtual     void        processVolume_l();
+
+    virtual     void        dumpInternals(int fd, const Vector<String16>& args);
+
+    virtual     bool        isOutput() const { return true; }
+
+protected:
+
+                audio_stream_type_t         mStreamType;
+                float                       mMasterVolume;
+                float                       mStreamVolume;
+                bool                        mMasterMute;
+                bool                        mStreamMute;
+                float                       mHalVolFloat;
+                AudioStreamOut*             mOutput;
+};
+
+class MmapCaptureThread : public MmapThread
+{
+
+public:
+    MmapCaptureThread(const sp<AudioFlinger>& audioFlinger, audio_io_handle_t id,
+                      AudioHwDevice *hwDev, AudioStreamIn *input,
+                      audio_devices_t outDevice, audio_devices_t inDevice, bool systemReady);
+    virtual     ~MmapCaptureThread() {}
+
+                AudioStreamIn* clearInput();
+
+    virtual     bool           isOutput() const { return false; }
+
+protected:
+
+                AudioStreamIn*  mInput;
+};
diff --git a/services/audioflinger/TrackBase.h b/services/audioflinger/TrackBase.h
index 9ca2d63..e0c09f7 100644
--- a/services/audioflinger/TrackBase.h
+++ b/services/audioflinger/TrackBase.h
@@ -77,6 +77,7 @@
             audio_track_cblk_t* cblk() const { return mCblk; }
             audio_session_t sessionId() const { return mSessionId; }
             uid_t       uid() const { return mUid; }
+            audio_port_handle_t portId() const { return mPortId; }
     virtual status_t    setSyncEvent(const sp<SyncEvent>& event);
 
             sp<IMemory> getBuffers() const { return mBufferMemory; }
@@ -86,6 +87,10 @@
             bool        isPatchTrack() const { return (mType == TYPE_PATCH); }
             bool        isExternalTrack() const { return !isOutputTrack() && !isPatchTrack(); }
 
+    virtual void        invalidate() { mIsInvalid = true; }
+            bool        isInvalid() const { return mIsInvalid; }
+
+
 protected:
                         TrackBase(const TrackBase&);
                         TrackBase& operator = (const TrackBase&);
@@ -165,6 +170,7 @@
     track_type          mType;      // must be one of TYPE_DEFAULT, TYPE_OUTPUT, TYPE_PATCH ...
     audio_io_handle_t   mThreadIoHandle; // I/O handle of the thread the track is attached to
     audio_port_handle_t mPortId; // unique ID for this track used by audio policy
+    bool                mIsInvalid; // non-resettable latch, set by invalidate()
 };
 
 // PatchProxyBufferProvider interface is implemented by PatchTrack and PatchRecord.
diff --git a/services/audioflinger/Tracks.cpp b/services/audioflinger/Tracks.cpp
index 9746075..b03d003 100644
--- a/services/audioflinger/Tracks.cpp
+++ b/services/audioflinger/Tracks.cpp
@@ -98,7 +98,8 @@
         mTerminated(false),
         mType(type),
         mThreadIoHandle(thread->id()),
-        mPortId(portId)
+        mPortId(portId),
+        mIsInvalid(false)
 {
     const uid_t callingUid = IPCThreadState::self()->getCallingUid();
     if (!isTrustedCallingUid(callingUid) || clientUid == AUDIO_UID_INVALID) {
@@ -365,7 +366,6 @@
     // mSinkTimestamp
     mFastIndex(-1),
     mCachedVolume(1.0),
-    mIsInvalid(false),
     mResumeToStopping(false),
     mFlushHwPending(false),
     mFlags(flags)
@@ -1042,8 +1042,8 @@
 
 void AudioFlinger::PlaybackThread::Track::invalidate()
 {
+    TrackBase::invalidate();
     signalClientFlag(CBLK_INVALID);
-    mIsInvalid = true;
 }
 
 void AudioFlinger::PlaybackThread::Track::disable()
@@ -1599,6 +1599,7 @@
 
 void AudioFlinger::RecordThread::RecordTrack::invalidate()
 {
+    TrackBase::invalidate();
     // FIXME should use proxy, and needs work
     audio_track_cblk_t* cblk = mCblk;
     android_atomic_or(CBLK_INVALID, &cblk->mFlags);
@@ -1735,4 +1736,76 @@
     mProxy->releaseBuffer(buffer);
 }
 
+
+
+AudioFlinger::MmapThread::MmapTrack::MmapTrack(ThreadBase *thread,
+        uint32_t sampleRate,
+        audio_format_t format,
+        audio_channel_mask_t channelMask,
+        audio_session_t sessionId,
+        uid_t uid,
+        audio_port_handle_t portId)
+    :   TrackBase(thread, NULL, sampleRate, format,
+                  channelMask, 0, NULL, sessionId, uid, false,
+                  ALLOC_NONE,
+                  TYPE_DEFAULT, portId)
+{
+}
+
+AudioFlinger::MmapThread::MmapTrack::~MmapTrack()
+{
+}
+
+status_t AudioFlinger::MmapThread::MmapTrack::initCheck() const
+{
+    return NO_ERROR;
+}
+
+status_t AudioFlinger::MmapThread::MmapTrack::start(AudioSystem::sync_event_t event __unused,
+                                                        audio_session_t triggerSession __unused)
+{
+    return NO_ERROR;
+}
+
+void AudioFlinger::MmapThread::MmapTrack::stop()
+{
+}
+
+// AudioBufferProvider interface
+status_t AudioFlinger::MmapThread::MmapTrack::getNextBuffer(AudioBufferProvider::Buffer* buffer)
+{
+    buffer->frameCount = 0;
+    buffer->raw = nullptr;
+    return INVALID_OPERATION;
+}
+
+// ExtendedAudioBufferProvider interface
+size_t AudioFlinger::MmapThread::MmapTrack::framesReady() const {
+    return 0;
+}
+
+int64_t AudioFlinger::MmapThread::MmapTrack::framesReleased() const
+{
+    return 0;
+}
+
+void AudioFlinger::MmapThread::MmapTrack::onTimestamp(const ExtendedTimestamp &timestamp __unused)
+{
+}
+
+/*static*/ void AudioFlinger::MmapThread::MmapTrack::appendDumpHeader(String8& result)
+{
+    result.append("    Client Fmt Chn mask  SRate\n");
+}
+
+void AudioFlinger::MmapThread::MmapTrack::dump(char* buffer, size_t size)
+{
+    snprintf(buffer, size, "            %6u %3u    %08X %5u\n",
+            mUid,
+            mFormat,
+            mChannelMask,
+            mSampleRate);
+
+}
+
 } // namespace android