merge in jb-mr2-release history after reset to jb-mr2-dev
diff --git a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
index c34690b..00f6de3 100644
--- a/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
+++ b/drm/mediadrm/plugins/mock/MockDrmCryptoPlugin.cpp
@@ -343,7 +343,31 @@
         Mutex::Autolock lock(mLock);
         ALOGD("MockDrmPlugin::setPropertyString(name=%s, value=%s)",
               name.string(), value.string());
-        mStringProperties.add(name, value);
+
+        if (name == "mock-send-event") {
+            unsigned code, extra;
+            sscanf(value.string(), "%d %d", &code, &extra);
+            DrmPlugin::EventType eventType = (DrmPlugin::EventType)code;
+
+            Vector<uint8_t> const *pSessionId = NULL;
+            ssize_t index = mByteArrayProperties.indexOfKey(String8("mock-event-session-id"));
+            if (index >= 0) {
+                pSessionId = &mByteArrayProperties[index];
+            }
+
+            Vector<uint8_t> const *pData = NULL;
+            index = mByteArrayProperties.indexOfKey(String8("mock-event-data"));
+            if (index >= 0) {
+                pData = &mByteArrayProperties[index];
+            }
+            ALOGD("sending event from mock drm plugin: %d %d %s %s",
+                  (int)code, extra, pSessionId ? vectorToString(*pSessionId) : "{}",
+                  pData ? vectorToString(*pData) : "{}");
+
+            sendEvent(eventType, extra, pSessionId, pData);
+        } else {
+            mStringProperties.add(name, value);
+        }
         return OK;
     }
 
diff --git a/include/media/IDrm.h b/include/media/IDrm.h
index 15d0a75..d630c40 100644
--- a/include/media/IDrm.h
+++ b/include/media/IDrm.h
@@ -17,6 +17,7 @@
 #include <binder/IInterface.h>
 #include <media/stagefright/foundation/ABase.h>
 #include <media/drm/DrmAPI.h>
+#include <media/IDrmClient.h>
 
 #ifndef ANDROID_IDRM_H_
 
@@ -106,6 +107,8 @@
                             Vector<uint8_t> const &signature,
                             bool &match) = 0;
 
+    virtual status_t setListener(const sp<IDrmClient>& listener) = 0;
+
 private:
     DISALLOW_EVIL_CONSTRUCTORS(IDrm);
 };
diff --git a/include/media/IDrmClient.h b/include/media/IDrmClient.h
new file mode 100644
index 0000000..3b2fc7c
--- /dev/null
+++ b/include/media/IDrmClient.h
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2013 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 ANDROID_IDRMCLIENT_H
+#define ANDROID_IDRMCLIENT_H
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+#include <media/drm/DrmAPI.h>
+
+namespace android {
+
+class IDrmClient: public IInterface
+{
+public:
+    DECLARE_META_INTERFACE(DrmClient);
+
+    virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj) = 0;
+};
+
+// ----------------------------------------------------------------------------
+
+class BnDrmClient: public BnInterface<IDrmClient>
+{
+public:
+    virtual status_t onTransact(uint32_t code,
+                                const Parcel& data,
+                                Parcel* reply,
+                                uint32_t flags = 0);
+};
+
+}; // namespace android
+
+#endif // ANDROID_IDRMCLIENT_H
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index 1ada9c3..fbe71ad 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -20,6 +20,7 @@
     IAudioRecord.cpp \
     ICrypto.cpp \
     IDrm.cpp \
+    IDrmClient.cpp \
     IHDCP.cpp \
     AudioRecord.cpp \
     AudioSystem.cpp \
diff --git a/media/libmedia/IDrm.cpp b/media/libmedia/IDrm.cpp
index 1641b56..1578846 100644
--- a/media/libmedia/IDrm.cpp
+++ b/media/libmedia/IDrm.cpp
@@ -51,7 +51,8 @@
     ENCRYPT,
     DECRYPT,
     SIGN,
-    VERIFY
+    VERIFY,
+    SET_LISTENER
 };
 
 struct BpDrm : public BpInterface<IDrm> {
@@ -384,6 +385,14 @@
         return reply.readInt32();
     }
 
+    virtual status_t setListener(const sp<IDrmClient>& listener) {
+        Parcel data, reply;
+        data.writeInterfaceToken(IDrm::getInterfaceDescriptor());
+        data.writeStrongBinder(listener->asBinder());
+        remote()->transact(SET_LISTENER, data, &reply);
+        return reply.readInt32();
+    }
+
 private:
     void readVector(Parcel &reply, Vector<uint8_t> &vector) const {
         uint32_t size = reply.readInt32();
@@ -712,6 +721,14 @@
             return OK;
         }
 
+    case SET_LISTENER: {
+        CHECK_INTERFACE(IDrm, data, reply);
+        sp<IDrmClient> listener =
+            interface_cast<IDrmClient>(data.readStrongBinder());
+        reply->writeInt32(setListener(listener));
+        return NO_ERROR;
+    } break;
+
     default:
         return BBinder::onTransact(code, data, reply, flags);
     }
diff --git a/media/libmedia/IDrmClient.cpp b/media/libmedia/IDrmClient.cpp
new file mode 100644
index 0000000..f50715e
--- /dev/null
+++ b/media/libmedia/IDrmClient.cpp
@@ -0,0 +1,81 @@
+/*
+**
+** Copyright 2013, 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.
+*/
+
+//#define LOG_NDEBUG 0
+#define LOG_TAG "IDrmClient"
+#include <utils/Log.h>
+
+#include <utils/RefBase.h>
+#include <binder/IInterface.h>
+#include <binder/Parcel.h>
+
+#include <media/IMediaPlayerClient.h>
+#include <media/IDrmClient.h>
+
+namespace android {
+
+enum {
+    NOTIFY = IBinder::FIRST_CALL_TRANSACTION,
+};
+
+class BpDrmClient: public BpInterface<IDrmClient>
+{
+public:
+    BpDrmClient(const sp<IBinder>& impl)
+        : BpInterface<IDrmClient>(impl)
+    {
+    }
+
+    virtual void notify(DrmPlugin::EventType eventType, int extra, const Parcel *obj)
+    {
+        Parcel data, reply;
+        data.writeInterfaceToken(IDrmClient::getInterfaceDescriptor());
+        data.writeInt32((int)eventType);
+        data.writeInt32(extra);
+        if (obj && obj->dataSize() > 0) {
+            data.appendFrom(const_cast<Parcel *>(obj), 0, obj->dataSize());
+        }
+        remote()->transact(NOTIFY, data, &reply, IBinder::FLAG_ONEWAY);
+    }
+};
+
+IMPLEMENT_META_INTERFACE(DrmClient, "android.media.IDrmClient");
+
+// ----------------------------------------------------------------------
+
+status_t BnDrmClient::onTransact(
+    uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
+{
+    switch (code) {
+        case NOTIFY: {
+            CHECK_INTERFACE(IDrmClient, data, reply);
+            int eventType = data.readInt32();
+            int extra = data.readInt32();
+            Parcel obj;
+            if (data.dataAvail() > 0) {
+                obj.appendFrom(const_cast<Parcel *>(&data), data.dataPosition(), data.dataAvail());
+            }
+
+            notify((DrmPlugin::EventType)eventType, extra, &obj);
+            return NO_ERROR;
+        } break;
+        default:
+            return BBinder::onTransact(code, data, reply, flags);
+    }
+}
+
+}; // namespace android
diff --git a/media/libmediaplayerservice/Drm.cpp b/media/libmediaplayerservice/Drm.cpp
index 5fdb9f4..1e6cd94 100644
--- a/media/libmediaplayerservice/Drm.cpp
+++ b/media/libmediaplayerservice/Drm.cpp
@@ -47,6 +47,7 @@
 
 Drm::Drm()
     : mInitCheck(NO_INIT),
+      mListener(NULL),
       mFactory(NULL),
       mPlugin(NULL) {
 }
@@ -67,6 +68,41 @@
     return mInitCheck;
 }
 
+status_t Drm::setListener(const sp<IDrmClient>& listener)
+{
+    Mutex::Autolock lock(mEventLock);
+    mListener = listener;
+    return NO_ERROR;
+}
+
+void Drm::sendEvent(DrmPlugin::EventType eventType, int extra,
+                    Vector<uint8_t> const *sessionId,
+                    Vector<uint8_t> const *data)
+{
+    mEventLock.lock();
+    sp<IDrmClient> listener = mListener;
+    mEventLock.unlock();
+
+    if (listener != NULL) {
+        Parcel obj;
+        if (sessionId && sessionId->size()) {
+            obj.writeInt32(sessionId->size());
+            obj.write(sessionId->array(), sessionId->size());
+        } else {
+            obj.writeInt32(0);
+        }
+
+        if (data && data->size()) {
+            obj.writeInt32(data->size());
+            obj.write(data->array(), data->size());
+        } else {
+            obj.writeInt32(0);
+        }
+
+        Mutex::Autolock lock(mNotifyLock);
+        listener->notify(eventType, extra, &obj);
+    }
+}
 
 /*
  * Search the plugins directory for a plugin that supports the scheme
@@ -195,7 +231,9 @@
         return mInitCheck;
     }
 
-    return mFactory->createDrmPlugin(uuid, &mPlugin);
+    status_t result = mFactory->createDrmPlugin(uuid, &mPlugin);
+    mPlugin->setListener(this);
+    return result;
 }
 
 status_t Drm::destroyPlugin() {
diff --git a/media/libmediaplayerservice/Drm.h b/media/libmediaplayerservice/Drm.h
index f24921e..3da8ad4 100644
--- a/media/libmediaplayerservice/Drm.h
+++ b/media/libmediaplayerservice/Drm.h
@@ -21,6 +21,7 @@
 #include "SharedLibrary.h"
 
 #include <media/IDrm.h>
+#include <media/IDrmClient.h>
 #include <utils/threads.h>
 
 namespace android {
@@ -28,7 +29,7 @@
 struct DrmFactory;
 struct DrmPlugin;
 
-struct Drm : public BnDrm {
+struct Drm : public BnDrm, public DrmPluginListener {
     Drm();
     virtual ~Drm();
 
@@ -108,10 +109,21 @@
                             Vector<uint8_t> const &signature,
                             bool &match);
 
+    virtual status_t setListener(const sp<IDrmClient>& listener);
+
+    virtual void sendEvent(DrmPlugin::EventType eventType, int extra,
+                           Vector<uint8_t> const *sessionId,
+                           Vector<uint8_t> const *data);
+
 private:
     mutable Mutex mLock;
 
     status_t mInitCheck;
+
+    sp<IDrmClient> mListener;
+    mutable Mutex mEventLock;
+    mutable Mutex mNotifyLock;
+
     sp<SharedLibrary> mLibrary;
     DrmFactory *mFactory;
     DrmPlugin *mPlugin;
diff --git a/media/libstagefright/ACodec.cpp b/media/libstagefright/ACodec.cpp
index c9f8741..ff72b71 100644
--- a/media/libstagefright/ACodec.cpp
+++ b/media/libstagefright/ACodec.cpp
@@ -3416,6 +3416,21 @@
             return true;
         }
 
+        case kWhatResume:
+        {
+            // We'll be active soon enough.
+            return true;
+        }
+
+        case kWhatFlush:
+        {
+            // We haven't even started yet, so we're flushed alright...
+            sp<AMessage> notify = mCodec->mNotify->dup();
+            notify->setInt32("what", ACodec::kWhatFlushCompleted);
+            notify->post();
+            return true;
+        }
+
         default:
             return BaseState::onMessageReceived(msg);
     }
diff --git a/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp b/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
index 7719435..5749733 100644
--- a/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
+++ b/media/libstagefright/codecs/aacenc/SoftAACEncoder2.cpp
@@ -481,7 +481,7 @@
 
         void* inBuffer[]        = { (unsigned char *)mInputFrame };
         INT   inBufferIds[]     = { IN_AUDIO_DATA };
-        INT   inBufferSize[]    = { numBytesPerInputFrame };
+        INT   inBufferSize[]    = { (INT)numBytesPerInputFrame };
         INT   inBufferElSize[]  = { sizeof(int16_t) };
 
         AACENC_BufDesc inBufDesc;
diff --git a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
index 07f8b4f..50b739c 100644
--- a/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
+++ b/media/libstagefright/codecs/amrnb/enc/SoftAMRNBEncoder.cpp
@@ -257,7 +257,7 @@
             }
 
             if (pcmParams->nChannels != 1
-                    || pcmParams->nSamplingRate != kSampleRate) {
+                    || pcmParams->nSamplingRate != (OMX_U32)kSampleRate) {
                 return OMX_ErrorUndefined;
             }
 
diff --git a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
index cc38dc3..e25637a 100644
--- a/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
+++ b/media/libstagefright/codecs/on2/enc/SoftVPXEncoder.cpp
@@ -655,7 +655,8 @@
         vpx_codec_iter_t encoded_packet_iterator = NULL;
         const vpx_codec_cx_pkt_t* encoded_packet;
 
-        while (encoded_packet = vpx_codec_get_cx_data(mCodecContext, &encoded_packet_iterator)) {
+        while ((encoded_packet = vpx_codec_get_cx_data(
+                        mCodecContext, &encoded_packet_iterator))) {
             if (encoded_packet->kind == VPX_CODEC_CX_FRAME_PKT) {
                 outputBufferHeader->nTimeStamp = encoded_packet->data.frame.pts;
                 outputBufferHeader->nFlags = 0;
diff --git a/media/libstagefright/mpeg2ts/ESQueue.cpp b/media/libstagefright/mpeg2ts/ESQueue.cpp
index 9499712..9f3b19c 100644
--- a/media/libstagefright/mpeg2ts/ESQueue.cpp
+++ b/media/libstagefright/mpeg2ts/ESQueue.cpp
@@ -147,9 +147,9 @@
                 }
 
                 if (startOffset > 0) {
-                    ALOGI("found something resembling an H.264/MPEG syncword at "
-                         "offset %ld",
-                         startOffset);
+                    ALOGI("found something resembling an H.264/MPEG syncword "
+                          "at offset %d",
+                          startOffset);
                 }
 
                 data = &ptr[startOffset];
@@ -180,9 +180,9 @@
                 }
 
                 if (startOffset > 0) {
-                    ALOGI("found something resembling an H.264/MPEG syncword at "
-                         "offset %ld",
-                         startOffset);
+                    ALOGI("found something resembling an H.264/MPEG syncword "
+                          "at offset %d",
+                          startOffset);
                 }
 
                 data = &ptr[startOffset];
@@ -213,8 +213,9 @@
                 }
 
                 if (startOffset > 0) {
-                    ALOGI("found something resembling an AAC syncword at offset %ld",
-                         startOffset);
+                    ALOGI("found something resembling an AAC syncword at "
+                          "offset %d",
+                          startOffset);
                 }
 
                 data = &ptr[startOffset];
@@ -241,8 +242,8 @@
 
                 if (startOffset > 0) {
                     ALOGI("found something resembling an MPEG audio "
-                         "syncword at offset %ld",
-                         startOffset);
+                          "syncword at offset %d",
+                          startOffset);
                 }
 
                 data = &ptr[startOffset];
@@ -394,10 +395,30 @@
 }
 
 sp<ABuffer> ElementaryStreamQueue::dequeueAccessUnitAAC() {
-    int64_t timeUs;
+    if (mBuffer->size() == 0) {
+        return NULL;
+    }
 
+    CHECK(!mRangeInfos.empty());
+
+    const RangeInfo &info = *mRangeInfos.begin();
+    if (mBuffer->size() < info.mLength) {
+        return NULL;
+    }
+
+    CHECK_GE(info.mTimestampUs, 0ll);
+
+    // The idea here is consume all AAC frames starting at offsets before
+    // info.mLength so we can assign a meaningful timestamp without
+    // having to interpolate.
+    // The final AAC frame may well extend into the next RangeInfo but
+    // that's ok.
     size_t offset = 0;
-    while (offset + 7 <= mBuffer->size()) {
+    while (offset < info.mLength) {
+        if (offset + 7 > mBuffer->size()) {
+            return NULL;
+        }
+
         ABitReader bits(mBuffer->data() + offset, mBuffer->size() - offset);
 
         // adts_fixed_header
@@ -450,24 +471,15 @@
         }
 
         if (offset + aac_frame_length > mBuffer->size()) {
-            break;
+            return NULL;
         }
 
         size_t headerSize = protection_absent ? 7 : 9;
 
-        int64_t tmpUs = fetchTimestamp(aac_frame_length);
-        CHECK_GE(tmpUs, 0ll);
-
-        if (offset == 0) {
-            timeUs = tmpUs;
-        }
-
         offset += aac_frame_length;
     }
 
-    if (offset == 0) {
-        return NULL;
-    }
+    int64_t timeUs = fetchTimestamp(offset);
 
     sp<ABuffer> accessUnit = new ABuffer(offset);
     memcpy(accessUnit->data(), mBuffer->data(), offset);
@@ -492,7 +504,6 @@
 
         if (first) {
             timeUs = info->mTimestampUs;
-            first = false;
         }
 
         if (info->mLength > size) {
@@ -509,6 +520,8 @@
             mRangeInfos.erase(mRangeInfos.begin());
             info = NULL;
         }
+
+        first = false;
     }
 
     if (timeUs == 0ll) {
diff --git a/media/libstagefright/wifi-display/ANetworkSession.cpp b/media/libstagefright/wifi-display/ANetworkSession.cpp
index 88ca1cc..f074438 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.cpp
+++ b/media/libstagefright/wifi-display/ANetworkSession.cpp
@@ -451,24 +451,6 @@
             const Fragment &frag = *mOutFragments.begin();
             const sp<ABuffer> &datagram = frag.mBuffer;
 
-            uint8_t *data = datagram->data();
-            if (data[0] == 0x80 && (data[1] & 0x7f) == 33) {
-                int64_t nowUs = ALooper::GetNowUs();
-
-                uint32_t prevRtpTime = U32_AT(&data[4]);
-
-                // 90kHz time scale
-                uint32_t rtpTime = (nowUs * 9ll) / 100ll;
-                int32_t diffTime = (int32_t)rtpTime - (int32_t)prevRtpTime;
-
-                ALOGV("correcting rtpTime by %.0f ms", diffTime / 90.0);
-
-                data[4] = rtpTime >> 24;
-                data[5] = (rtpTime >> 16) & 0xff;
-                data[6] = (rtpTime >> 8) & 0xff;
-                data[7] = rtpTime & 0xff;
-            }
-
             int n;
             do {
                 n = send(mSocket, datagram->data(), datagram->size(), 0);
@@ -874,6 +856,14 @@
             err = -errno;
             goto bail2;
         }
+
+        int tos = 224;  // VOICE
+        res = setsockopt(s, IPPROTO_IP, IP_TOS, &tos, sizeof(tos));
+
+        if (res < 0) {
+            err = -errno;
+            goto bail2;
+        }
     }
 
     err = MakeSocketNonBlocking(s);
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index f1f9f45..1578c21 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -109,3 +109,25 @@
 LOCAL_MODULE_TAGS := debug
 
 include $(BUILD_EXECUTABLE)
+
+################################################################################
+
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+        nettest.cpp                     \
+
+LOCAL_SHARED_LIBRARIES:= \
+        libbinder                       \
+        libgui                          \
+        libmedia                        \
+        libstagefright                  \
+        libstagefright_foundation       \
+        libstagefright_wfd              \
+        libutils                        \
+
+LOCAL_MODULE:= nettest
+
+LOCAL_MODULE_TAGS := debug
+
+include $(BUILD_EXECUTABLE)
diff --git a/media/libstagefright/wifi-display/TimeSyncer.cpp b/media/libstagefright/wifi-display/TimeSyncer.cpp
index 64e182e..cb429bc 100644
--- a/media/libstagefright/wifi-display/TimeSyncer.cpp
+++ b/media/libstagefright/wifi-display/TimeSyncer.cpp
@@ -102,6 +102,10 @@
 
         case kWhatSendPacket:
         {
+            if (mHistory.size() == 0) {
+                ALOGI("starting batch");
+            }
+
             TimeInfo ti;
             memset(&ti, 0, sizeof(ti));
 
@@ -229,6 +233,8 @@
                         } else {
                             notifyOffset();
 
+                            ALOGI("batch done");
+
                             mHistory.clear();
                             postSendPacket(kBatchDelayUs);
                         }
diff --git a/media/libstagefright/wifi-display/TimeSyncer.h b/media/libstagefright/wifi-display/TimeSyncer.h
index 0e3aed7..4e7571f 100644
--- a/media/libstagefright/wifi-display/TimeSyncer.h
+++ b/media/libstagefright/wifi-display/TimeSyncer.h
@@ -75,7 +75,7 @@
         kNumPacketsPerBatch = 30,
     };
     static const int64_t kTimeoutDelayUs = 500000ll;
-    static const int64_t kBatchDelayUs = 10000000ll;  // every 10 secs
+    static const int64_t kBatchDelayUs = 60000000ll;  // every minute
 
     sp<ANetworkSession> mNetSession;
     sp<AMessage> mNotify;
diff --git a/media/libstagefright/wifi-display/nettest.cpp b/media/libstagefright/wifi-display/nettest.cpp
new file mode 100644
index 0000000..0779bf5
--- /dev/null
+++ b/media/libstagefright/wifi-display/nettest.cpp
@@ -0,0 +1,400 @@
+/*
+ * Copyright 2013, 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.
+ */
+
+//#define LOG_NEBUG 0
+#define LOG_TAG "nettest"
+#include <utils/Log.h>
+
+#include "ANetworkSession.h"
+#include "TimeSyncer.h"
+
+#include <binder/ProcessState.h>
+#include <media/stagefright/foundation/ABuffer.h>
+#include <media/stagefright/foundation/ADebug.h>
+#include <media/stagefright/foundation/AHandler.h>
+#include <media/stagefright/foundation/ALooper.h>
+#include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaDefs.h>
+#include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+struct TestHandler : public AHandler {
+    TestHandler(const sp<ANetworkSession> &netSession);
+
+    void listen(int32_t port);
+    void connect(const char *host, int32_t port);
+
+protected:
+    virtual ~TestHandler();
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
+private:
+    enum {
+        kTimeSyncerPort = 8123,
+    };
+
+    enum {
+        kWhatListen,
+        kWhatConnect,
+        kWhatTimeSyncerNotify,
+        kWhatNetNotify,
+        kWhatSendMore,
+        kWhatStop,
+    };
+
+    sp<ANetworkSession> mNetSession;
+    sp<TimeSyncer> mTimeSyncer;
+
+    int32_t mServerSessionID;
+    int32_t mSessionID;
+
+    int64_t mTimeOffsetUs;
+    bool mTimeOffsetValid;
+
+    int32_t mCounter;
+
+    int64_t mMaxDelayMs;
+
+    void dumpDelay(int32_t counter, int64_t delayMs);
+
+    DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
+};
+
+TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
+    : mNetSession(netSession),
+      mServerSessionID(0),
+      mSessionID(0),
+      mTimeOffsetUs(-1ll),
+      mTimeOffsetValid(false),
+      mCounter(0),
+      mMaxDelayMs(-1ll) {
+}
+
+TestHandler::~TestHandler() {
+}
+
+void TestHandler::listen(int32_t port) {
+    sp<AMessage> msg = new AMessage(kWhatListen, id());
+    msg->setInt32("port", port);
+    msg->post();
+}
+
+void TestHandler::connect(const char *host, int32_t port) {
+    sp<AMessage> msg = new AMessage(kWhatConnect, id());
+    msg->setString("host", host);
+    msg->setInt32("port", port);
+    msg->post();
+}
+
+void TestHandler::dumpDelay(int32_t counter, int64_t delayMs) {
+    static const int64_t kMinDelayMs = 0;
+    static const int64_t kMaxDelayMs = 300;
+
+    const char *kPattern = "########################################";
+    size_t kPatternSize = strlen(kPattern);
+
+    int n = (kPatternSize * (delayMs - kMinDelayMs))
+                / (kMaxDelayMs - kMinDelayMs);
+
+    if (n < 0) {
+        n = 0;
+    } else if ((size_t)n > kPatternSize) {
+        n = kPatternSize;
+    }
+
+    if (delayMs > mMaxDelayMs) {
+        mMaxDelayMs = delayMs;
+    }
+
+    ALOGI("[%d] (%4lld ms / %4lld ms) %s",
+          counter,
+          delayMs,
+          mMaxDelayMs,
+          kPattern + kPatternSize - n);
+}
+
+void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatListen:
+        {
+            sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+            mTimeSyncer = new TimeSyncer(mNetSession, notify);
+            looper()->registerHandler(mTimeSyncer);
+
+            notify = new AMessage(kWhatNetNotify, id());
+
+            int32_t port;
+            CHECK(msg->findInt32("port", &port));
+
+            struct in_addr ifaceAddr;
+            ifaceAddr.s_addr = INADDR_ANY;
+
+            CHECK_EQ((status_t)OK,
+                     mNetSession->createTCPDatagramSession(
+                         ifaceAddr,
+                         port,
+                         notify,
+                         &mServerSessionID));
+            break;
+        }
+
+        case kWhatConnect:
+        {
+            sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+            mTimeSyncer = new TimeSyncer(mNetSession, notify);
+            looper()->registerHandler(mTimeSyncer);
+            mTimeSyncer->startServer(kTimeSyncerPort);
+
+            AString host;
+            CHECK(msg->findString("host", &host));
+
+            int32_t port;
+            CHECK(msg->findInt32("port", &port));
+
+            notify = new AMessage(kWhatNetNotify, id());
+
+            CHECK_EQ((status_t)OK,
+                     mNetSession->createTCPDatagramSession(
+                         0 /* localPort */,
+                         host.c_str(),
+                         port,
+                         notify,
+                         &mSessionID));
+            break;
+        }
+
+        case kWhatNetNotify:
+        {
+            int32_t reason;
+            CHECK(msg->findInt32("reason", &reason));
+
+            switch (reason) {
+                case ANetworkSession::kWhatConnected:
+                {
+                    ALOGI("kWhatConnected");
+
+                    (new AMessage(kWhatSendMore, id()))->post();
+                    break;
+                }
+
+                case ANetworkSession::kWhatClientConnected:
+                {
+                    ALOGI("kWhatClientConnected");
+
+                    CHECK_EQ(mSessionID, 0);
+                    CHECK(msg->findInt32("sessionID", &mSessionID));
+
+                    AString clientIP;
+                    CHECK(msg->findString("client-ip", &clientIP));
+
+                    mTimeSyncer->startClient(clientIP.c_str(), kTimeSyncerPort);
+                    break;
+                }
+
+                case ANetworkSession::kWhatDatagram:
+                {
+                    sp<ABuffer> packet;
+                    CHECK(msg->findBuffer("data", &packet));
+
+                    CHECK_EQ(packet->size(), 12u);
+
+                    int32_t counter = U32_AT(packet->data());
+                    int64_t timeUs = U64_AT(packet->data() + 4);
+
+                    if (mTimeOffsetValid) {
+                        timeUs -= mTimeOffsetUs;
+                        int64_t nowUs = ALooper::GetNowUs();
+                        int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+                        dumpDelay(counter, delayMs);
+                    } else {
+                        ALOGI("received %d", counter);
+                    }
+                    break;
+                }
+
+                case ANetworkSession::kWhatError:
+                {
+                    ALOGE("kWhatError");
+                    break;
+                }
+
+                default:
+                    TRESPASS();
+            }
+            break;
+        }
+
+        case kWhatTimeSyncerNotify:
+        {
+            CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+            mTimeOffsetValid = true;
+            break;
+        }
+
+        case kWhatSendMore:
+        {
+            uint8_t buffer[4 + 8];
+            buffer[0] = mCounter >> 24;
+            buffer[1] = (mCounter >> 16) & 0xff;
+            buffer[2] = (mCounter >> 8) & 0xff;
+            buffer[3] = mCounter & 0xff;
+
+            int64_t nowUs = ALooper::GetNowUs();
+
+            buffer[4] = nowUs >> 56;
+            buffer[5] = (nowUs >> 48) & 0xff;
+            buffer[6] = (nowUs >> 40) & 0xff;
+            buffer[7] = (nowUs >> 32) & 0xff;
+            buffer[8] = (nowUs >> 24) & 0xff;
+            buffer[9] = (nowUs >> 16) & 0xff;
+            buffer[10] = (nowUs >> 8) & 0xff;
+            buffer[11] = nowUs & 0xff;
+
+            ++mCounter;
+
+            CHECK_EQ((status_t)OK,
+                     mNetSession->sendRequest(
+                         mSessionID,
+                         buffer,
+                         sizeof(buffer),
+                         true /* timeValid */,
+                         nowUs));
+
+            msg->post(100000ll);
+            break;
+        }
+
+        case kWhatStop:
+        {
+            if (mSessionID != 0) {
+                mNetSession->destroySession(mSessionID);
+                mSessionID = 0;
+            }
+
+            if (mServerSessionID != 0) {
+                mNetSession->destroySession(mServerSessionID);
+                mServerSessionID = 0;
+            }
+
+            looper()->stop();
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
+}  // namespace android
+
+static void usage(const char *me) {
+    fprintf(stderr,
+            "usage: %s -c host:port\tconnect to remote host\n"
+            "               -l port   \tlisten\n",
+            me);
+}
+
+int main(int argc, char **argv) {
+    using namespace android;
+
+    // srand(time(NULL));
+
+    ProcessState::self()->startThreadPool();
+
+    DataSource::RegisterDefaultSniffers();
+
+    int32_t connectToPort = -1;
+    AString connectToHost;
+
+    int32_t listenOnPort = -1;
+
+    int res;
+    while ((res = getopt(argc, argv, "hc:l:")) >= 0) {
+        switch (res) {
+            case 'c':
+            {
+                const char *colonPos = strrchr(optarg, ':');
+
+                if (colonPos == NULL) {
+                    usage(argv[0]);
+                    exit(1);
+                }
+
+                connectToHost.setTo(optarg, colonPos - optarg);
+
+                char *end;
+                connectToPort = strtol(colonPos + 1, &end, 10);
+
+                if (*end != '\0' || end == colonPos + 1
+                        || connectToPort < 0 || connectToPort > 65535) {
+                    fprintf(stderr, "Illegal port specified.\n");
+                    exit(1);
+                }
+                break;
+            }
+
+            case 'l':
+            {
+                char *end;
+                listenOnPort = strtol(optarg, &end, 10);
+
+                if (*end != '\0' || end == optarg
+                        || listenOnPort < 0 || listenOnPort > 65535) {
+                    fprintf(stderr, "Illegal port specified.\n");
+                    exit(1);
+                }
+                break;
+            }
+
+            case '?':
+            case 'h':
+                usage(argv[0]);
+                exit(1);
+        }
+    }
+
+    if ((listenOnPort < 0 && connectToPort < 0)
+            || (listenOnPort >= 0 && connectToPort >= 0)) {
+        fprintf(stderr,
+                "You need to select either client or server mode.\n");
+        exit(1);
+    }
+
+    sp<ANetworkSession> netSession = new ANetworkSession;
+    netSession->start();
+
+    sp<ALooper> looper = new ALooper;
+
+    sp<TestHandler> handler = new TestHandler(netSession);
+    looper->registerHandler(handler);
+
+    if (listenOnPort) {
+        handler->listen(listenOnPort);
+    }
+
+    if (connectToPort >= 0) {
+        handler->connect(connectToHost.c_str(), connectToPort);
+    }
+
+    looper->start(true /* runOnCallingThread */);
+
+    return 0;
+}
diff --git a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
index 5f189e7..7a96081 100644
--- a/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPAssembler.cpp
@@ -40,7 +40,6 @@
     notify->setInt32("followsDiscontinuity", followsDiscontinuity);
     notify->post();
 }
-
 ////////////////////////////////////////////////////////////////////////////////
 
 RTPReceiver::TSAssembler::TSAssembler(const sp<AMessage> &notify)
diff --git a/media/libstagefright/wifi-display/rtp/RTPBase.h b/media/libstagefright/wifi-display/rtp/RTPBase.h
index e3fa845..6178f00 100644
--- a/media/libstagefright/wifi-display/rtp/RTPBase.h
+++ b/media/libstagefright/wifi-display/rtp/RTPBase.h
@@ -25,6 +25,7 @@
         PACKETIZATION_TRANSPORT_STREAM,
         PACKETIZATION_H264,
         PACKETIZATION_AAC,
+        PACKETIZATION_NONE,
     };
 
     enum TransportMode {
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
index 238fb82..8fa1dae 100644
--- a/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.cpp
@@ -30,11 +30,13 @@
 #include <media/stagefright/MediaErrors.h>
 #include <media/stagefright/Utils.h>
 
+#define TRACK_PACKET_LOSS       0
+
 namespace android {
 
 ////////////////////////////////////////////////////////////////////////////////
 
-struct RTPReceiver::Source : public RefBase {
+struct RTPReceiver::Source : public AHandler {
     Source(RTPReceiver *receiver, uint32_t ssrc);
 
     void onPacketReceived(uint16_t seq, const sp<ABuffer> &buffer);
@@ -44,7 +46,14 @@
 protected:
     virtual ~Source();
 
+    virtual void onMessageReceived(const sp<AMessage> &msg);
+
 private:
+    enum {
+        kWhatRetransmit,
+        kWhatDeclareLost,
+    };
+
     static const uint32_t kMinSequential = 2;
     static const uint32_t kMaxDropout = 3000;
     static const uint32_t kMaxMisorder = 100;
@@ -67,6 +76,17 @@
     // Ordered by extended seq number.
     List<sp<ABuffer> > mPackets;
 
+    enum StatusBits {
+        STATUS_DECLARED_LOST            = 1,
+        STATUS_REQUESTED_RETRANSMISSION = 2,
+        STATUS_ARRIVED_LATE             = 4,
+    };
+#if TRACK_PACKET_LOSS
+    KeyedVector<int32_t, uint32_t> mLostPackets;
+#endif
+
+    void modifyPacketStatus(int32_t extSeqNo, uint32_t mask);
+
     int32_t mAwaitingExtSeqNo;
     bool mRequestedRetransmission;
 
@@ -78,12 +98,20 @@
     int32_t mNumDeclaredLost;
     int32_t mNumDeclaredLostPrior;
 
+    int32_t mRetransmitGeneration;
+    int32_t mDeclareLostGeneration;
+    bool mDeclareLostTimerPending;
+
     void queuePacket(const sp<ABuffer> &packet);
     void dequeueMore();
 
     sp<ABuffer> getNextPacket();
     void resync();
 
+    void postRetransmitTimer(int64_t delayUs);
+    void postDeclareLostTimer(int64_t delayUs);
+    void cancelTimers();
+
     DISALLOW_EVIL_CONSTRUCTORS(Source);
 };
 
@@ -106,12 +134,71 @@
       mActivePacketType(-1),
       mNextReportTimeUs(-1ll),
       mNumDeclaredLost(0),
-      mNumDeclaredLostPrior(0) {
+      mNumDeclaredLostPrior(0),
+      mRetransmitGeneration(0),
+      mDeclareLostGeneration(0),
+      mDeclareLostTimerPending(false) {
 }
 
 RTPReceiver::Source::~Source() {
 }
 
+void RTPReceiver::Source::onMessageReceived(const sp<AMessage> &msg) {
+    switch (msg->what()) {
+        case kWhatRetransmit:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mRetransmitGeneration) {
+                break;
+            }
+
+            mRequestedRetransmission = true;
+            mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo);
+
+            modifyPacketStatus(
+                    mAwaitingExtSeqNo, STATUS_REQUESTED_RETRANSMISSION);
+            break;
+        }
+
+        case kWhatDeclareLost:
+        {
+            int32_t generation;
+            CHECK(msg->findInt32("generation", &generation));
+
+            if (generation != mDeclareLostGeneration) {
+                break;
+            }
+
+            cancelTimers();
+
+            ALOGV("Lost packet extSeqNo %d %s",
+                  mAwaitingExtSeqNo,
+                  mRequestedRetransmission ? "*" : "");
+
+            mRequestedRetransmission = false;
+            if (mActiveAssembler != NULL) {
+                mActiveAssembler->signalDiscontinuity();
+            }
+
+            modifyPacketStatus(mAwaitingExtSeqNo, STATUS_DECLARED_LOST);
+
+            // resync();
+            ++mAwaitingExtSeqNo;
+            ++mNumDeclaredLost;
+
+            mReceiver->notifyPacketLost();
+
+            dequeueMore();
+            break;
+        }
+
+        default:
+            TRESPASS();
+    }
+}
+
 void RTPReceiver::Source::onPacketReceived(
         uint16_t seq, const sp<ABuffer> &buffer) {
     if (mFirst) {
@@ -164,6 +251,8 @@
     if (mAwaitingExtSeqNo >= 0 && newExtendedSeqNo < mAwaitingExtSeqNo) {
         // We're no longer interested in these. They're old.
         ALOGV("dropping stale extSeqNo %d", newExtendedSeqNo);
+
+        modifyPacketStatus(newExtendedSeqNo, STATUS_ARRIVED_LATE);
         return;
     }
 
@@ -230,68 +319,34 @@
         }
 
         mNextReportTimeUs = nowUs + kReportIntervalUs;
+
+#if TRACK_PACKET_LOSS
+        for (size_t i = 0; i < mLostPackets.size(); ++i) {
+            int32_t key = mLostPackets.keyAt(i);
+            uint32_t value = mLostPackets.valueAt(i);
+
+            AString status;
+            if (value & STATUS_REQUESTED_RETRANSMISSION) {
+                status.append("retrans ");
+            }
+            if (value & STATUS_ARRIVED_LATE) {
+                status.append("arrived-late ");
+            }
+            ALOGI("Packet %d declared lost %s", key, status.c_str());
+        }
+#endif
     }
 
-    for (;;) {
-        sp<ABuffer> packet = getNextPacket();
-
-        if (packet == NULL) {
-            if (mPackets.empty()) {
-                break;
-            }
-
-            CHECK_GE(mAwaitingExtSeqNo, 0);
-
-            const sp<ABuffer> &firstPacket = *mPackets.begin();
-
-            uint32_t rtpTime;
-            CHECK(firstPacket->meta()->findInt32(
-                        "rtp-time", (int32_t *)&rtpTime));
-
-
-            int64_t rtpUs = (rtpTime * 100ll) / 9ll;
-
-            int64_t maxArrivalTimeUs =
-                mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs;
-
-            int64_t nowUs = ALooper::GetNowUs();
-
-            CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data());
-
-            ALOGV("waiting for %d, comparing against %d, %lld us left",
-                  mAwaitingExtSeqNo,
-                  firstPacket->int32Data(),
-                  maxArrivalTimeUs - nowUs);
-
-            if (maxArrivalTimeUs + kPacketLostAfterUs <= nowUs) {
-                ALOGV("Lost packet extSeqNo %d %s",
-                      mAwaitingExtSeqNo,
-                      mRequestedRetransmission ? "*" : "");
-
-                mRequestedRetransmission = false;
-                if (mActiveAssembler != NULL) {
-                    mActiveAssembler->signalDiscontinuity();
-                }
-
-                // resync();
-                ++mAwaitingExtSeqNo;
-                ++mNumDeclaredLost;
-
-                mReceiver->notifyPacketLost();
-                continue;
-            } else if (kRequestRetransmissionAfterUs > 0
-                    && maxArrivalTimeUs + kRequestRetransmissionAfterUs <= nowUs
-                    && !mRequestedRetransmission
-                    && mAwaitingExtSeqNo >= 0) {
-                mRequestedRetransmission = true;
-                mReceiver->requestRetransmission(mSSRC, mAwaitingExtSeqNo);
-                break;
-            } else {
-                break;
-            }
+    sp<ABuffer> packet;
+    while ((packet = getNextPacket()) != NULL) {
+        if (mDeclareLostTimerPending) {
+            cancelTimers();
         }
 
-        mRequestedRetransmission = false;
+        CHECK_GE(mAwaitingExtSeqNo, 0);
+#if TRACK_PACKET_LOSS
+        mLostPackets.removeItem(mAwaitingExtSeqNo);
+#endif
 
         int32_t packetType;
         CHECK(packet->meta()->findInt32("PT", &packetType));
@@ -301,14 +356,52 @@
             mActivePacketType = packetType;
         }
 
-        if (mActiveAssembler == NULL) {
-            continue;
+        if (mActiveAssembler != NULL) {
+            status_t err = mActiveAssembler->processPacket(packet);
+            if (err != OK) {
+                ALOGV("assembler returned error %d", err);
+            }
         }
 
-        status_t err = mActiveAssembler->processPacket(packet);
-        if (err != OK) {
-            ALOGV("assembler returned error %d", err);
-        }
+        ++mAwaitingExtSeqNo;
+    }
+
+    if (mDeclareLostTimerPending) {
+        return;
+    }
+
+    if (mPackets.empty()) {
+        return;
+    }
+
+    CHECK_GE(mAwaitingExtSeqNo, 0);
+
+    const sp<ABuffer> &firstPacket = *mPackets.begin();
+
+    uint32_t rtpTime;
+    CHECK(firstPacket->meta()->findInt32(
+                "rtp-time", (int32_t *)&rtpTime));
+
+
+    int64_t rtpUs = (rtpTime * 100ll) / 9ll;
+
+    int64_t maxArrivalTimeUs =
+        mFirstArrivalTimeUs + rtpUs - mFirstRTPTimeUs;
+
+    nowUs = ALooper::GetNowUs();
+
+    CHECK_LT(mAwaitingExtSeqNo, firstPacket->int32Data());
+
+    ALOGV("waiting for %d, comparing against %d, %lld us left",
+          mAwaitingExtSeqNo,
+          firstPacket->int32Data(),
+          maxArrivalTimeUs - nowUs);
+
+    postDeclareLostTimer(maxArrivalTimeUs + kPacketLostAfterUs);
+
+    if (kRequestRetransmissionAfterUs > 0ll) {
+        postRetransmitTimer(
+                maxArrivalTimeUs + kRequestRetransmissionAfterUs);
     }
 }
 
@@ -328,8 +421,6 @@
     sp<ABuffer> packet = *mPackets.begin();
     mPackets.erase(mPackets.begin());
 
-    ++mAwaitingExtSeqNo;
-
     return packet;
 }
 
@@ -404,9 +495,11 @@
 
 RTPReceiver::RTPReceiver(
         const sp<ANetworkSession> &netSession,
-        const sp<AMessage> &notify)
+        const sp<AMessage> &notify,
+        uint32_t flags)
     : mNetSession(netSession),
       mNotify(notify),
+      mFlags(flags),
       mRTPMode(TRANSPORT_UNDEFINED),
       mRTCPMode(TRANSPORT_UNDEFINED),
       mRTPSessionID(0),
@@ -693,6 +786,20 @@
             CHECK(msg->findBuffer("data", &data));
 
             if (isRTP) {
+                if (mFlags & FLAG_AUTO_CONNECT) {
+                    AString fromAddr;
+                    CHECK(msg->findString("fromAddr", &fromAddr));
+
+                    int32_t fromPort;
+                    CHECK(msg->findInt32("fromPort", &fromPort));
+
+                    CHECK_EQ((status_t)OK,
+                             connect(
+                                 fromAddr.c_str(), fromPort, fromPort + 1));
+
+                    mFlags &= ~FLAG_AUTO_CONNECT;
+                }
+
                 onRTPData(data);
             } else {
                 onRTCPData(data);
@@ -835,6 +942,8 @@
     sp<Source> source;
     if (index < 0) {
         source = new Source(this, srcId);
+        looper()->registerHandler(source);
+
         mSources.add(srcId, source);
     } else {
         source = mSources.valueAt(index);
@@ -965,6 +1074,7 @@
     PacketizationMode mode = mPacketTypes.valueAt(index);
 
     switch (mode) {
+        case PACKETIZATION_NONE:
         case PACKETIZATION_TRANSPORT_STREAM:
             return new TSAssembler(mNotify);
 
@@ -1005,5 +1115,39 @@
      mNetSession->sendRequest(mRTCPSessionID, buf->data(), buf->size());
 }
 
+void RTPReceiver::Source::modifyPacketStatus(int32_t extSeqNo, uint32_t mask) {
+#if TRACK_PACKET_LOSS
+    ssize_t index = mLostPackets.indexOfKey(extSeqNo);
+    if (index < 0) {
+        mLostPackets.add(extSeqNo, mask);
+    } else {
+        mLostPackets.editValueAt(index) |= mask;
+    }
+#endif
+}
+
+void RTPReceiver::Source::postRetransmitTimer(int64_t timeUs) {
+    int64_t delayUs = timeUs - ALooper::GetNowUs();
+    sp<AMessage> msg = new AMessage(kWhatRetransmit, id());
+    msg->setInt32("generation", mRetransmitGeneration);
+    msg->post(delayUs);
+}
+
+void RTPReceiver::Source::postDeclareLostTimer(int64_t timeUs) {
+    CHECK(!mDeclareLostTimerPending);
+    mDeclareLostTimerPending = true;
+
+    int64_t delayUs = timeUs - ALooper::GetNowUs();
+    sp<AMessage> msg = new AMessage(kWhatDeclareLost, id());
+    msg->setInt32("generation", mDeclareLostGeneration);
+    msg->post(delayUs);
+}
+
+void RTPReceiver::Source::cancelTimers() {
+    ++mRetransmitGeneration;
+    ++mDeclareLostGeneration;
+    mDeclareLostTimerPending = false;
+}
+
 }  // namespace android
 
diff --git a/media/libstagefright/wifi-display/rtp/RTPReceiver.h b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
index 630bce9..240ab2e 100644
--- a/media/libstagefright/wifi-display/rtp/RTPReceiver.h
+++ b/media/libstagefright/wifi-display/rtp/RTPReceiver.h
@@ -39,9 +39,14 @@
         kWhatAccessUnit,
         kWhatPacketLost,
     };
+
+    enum Flags {
+        FLAG_AUTO_CONNECT = 1,
+    };
     RTPReceiver(
             const sp<ANetworkSession> &netSession,
-            const sp<AMessage> &notify);
+            const sp<AMessage> &notify,
+            uint32_t flags = 0);
 
     status_t registerPacketType(
             uint8_t packetType, PacketizationMode mode);
@@ -82,6 +87,7 @@
 
     sp<ANetworkSession> mNetSession;
     sp<AMessage> mNotify;
+    uint32_t mFlags;
     TransportMode mRTPMode;
     TransportMode mRTCPMode;
     int32_t mRTPSessionID;
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
index 9eeeabd..6bbe650 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
@@ -187,6 +187,10 @@
     status_t err;
 
     switch (mode) {
+        case PACKETIZATION_NONE:
+            err = queueRawPacket(buffer, packetType);
+            break;
+
         case PACKETIZATION_TRANSPORT_STREAM:
             err = queueTSPackets(buffer, packetType);
             break;
@@ -202,6 +206,46 @@
     return err;
 }
 
+status_t RTPSender::queueRawPacket(
+        const sp<ABuffer> &packet, uint8_t packetType) {
+    CHECK_LE(packet->size(), kMaxUDPPacketSize - 12);
+
+    int64_t timeUs;
+    CHECK(packet->meta()->findInt64("timeUs", &timeUs));
+
+    sp<ABuffer> udpPacket = new ABuffer(12 + packet->size());
+
+    udpPacket->setInt32Data(mRTPSeqNo);
+
+    uint8_t *rtp = udpPacket->data();
+    rtp[0] = 0x80;
+    rtp[1] = packetType;
+
+    rtp[2] = (mRTPSeqNo >> 8) & 0xff;
+    rtp[3] = mRTPSeqNo & 0xff;
+    ++mRTPSeqNo;
+
+    uint32_t rtpTime = (timeUs * 9) / 100ll;
+
+    rtp[4] = rtpTime >> 24;
+    rtp[5] = (rtpTime >> 16) & 0xff;
+    rtp[6] = (rtpTime >> 8) & 0xff;
+    rtp[7] = rtpTime & 0xff;
+
+    rtp[8] = kSourceID >> 24;
+    rtp[9] = (kSourceID >> 16) & 0xff;
+    rtp[10] = (kSourceID >> 8) & 0xff;
+    rtp[11] = kSourceID & 0xff;
+
+    memcpy(&rtp[12], packet->data(), packet->size());
+
+    return sendRTPPacket(
+            udpPacket,
+            true /* storeInHistory */,
+            true /* timeValid */,
+            ALooper::GetNowUs());
+}
+
 status_t RTPSender::queueTSPackets(
         const sp<ABuffer> &tsPackets, uint8_t packetType) {
     CHECK_EQ(0, tsPackets->size() % 188);
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h
index 3a926ea..fefcab7 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.h
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.h
@@ -94,6 +94,7 @@
 
     static uint64_t GetNowNTP();
 
+    status_t queueRawPacket(const sp<ABuffer> &tsPackets, uint8_t packetType);
     status_t queueTSPackets(const sp<ABuffer> &tsPackets, uint8_t packetType);
     status_t queueAVCBuffer(const sp<ABuffer> &accessUnit, uint8_t packetType);
 
diff --git a/media/libstagefright/wifi-display/rtptest.cpp b/media/libstagefright/wifi-display/rtptest.cpp
index eade832..764a38b 100644
--- a/media/libstagefright/wifi-display/rtptest.cpp
+++ b/media/libstagefright/wifi-display/rtptest.cpp
@@ -21,6 +21,7 @@
 #include "ANetworkSession.h"
 #include "rtp/RTPSender.h"
 #include "rtp/RTPReceiver.h"
+#include "TimeSyncer.h"
 
 #include <binder/ProcessState.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -28,12 +29,116 @@
 #include <media/stagefright/foundation/AHandler.h>
 #include <media/stagefright/foundation/ALooper.h>
 #include <media/stagefright/foundation/AMessage.h>
+#include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/DataSource.h>
 #include <media/stagefright/MediaDefs.h>
 #include <media/stagefright/NuMediaExtractor.h>
+#include <media/stagefright/Utils.h>
+
+#define MEDIA_FILENAME "/sdcard/Frame Counter HD 30FPS_1080p.mp4"
 
 namespace android {
 
+struct PacketSource : public RefBase {
+    PacketSource() {}
+
+    virtual sp<ABuffer> getNextAccessUnit() = 0;
+
+protected:
+    virtual ~PacketSource() {}
+
+private:
+    DISALLOW_EVIL_CONSTRUCTORS(PacketSource);
+};
+
+struct MediaPacketSource : public PacketSource {
+    MediaPacketSource()
+        : mMaxSampleSize(1024 * 1024) {
+        mExtractor = new NuMediaExtractor;
+        CHECK_EQ((status_t)OK,
+                 mExtractor->setDataSource(MEDIA_FILENAME));
+
+        bool haveVideo = false;
+        for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
+            sp<AMessage> format;
+            CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format));
+
+            AString mime;
+            CHECK(format->findString("mime", &mime));
+
+            if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) {
+                mExtractor->selectTrack(i);
+                haveVideo = true;
+                break;
+            }
+        }
+
+        CHECK(haveVideo);
+    }
+
+    virtual sp<ABuffer> getNextAccessUnit() {
+        int64_t timeUs;
+        status_t err = mExtractor->getSampleTime(&timeUs);
+
+        if (err != OK) {
+            return NULL;
+        }
+
+        sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize);
+        CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit));
+
+        accessUnit->meta()->setInt64("timeUs", timeUs);
+
+        CHECK_EQ((status_t)OK, mExtractor->advance());
+
+        return accessUnit;
+    }
+
+protected:
+    virtual ~MediaPacketSource() {
+    }
+
+private:
+    sp<NuMediaExtractor> mExtractor;
+    size_t mMaxSampleSize;
+
+    DISALLOW_EVIL_CONSTRUCTORS(MediaPacketSource);
+};
+
+struct SimplePacketSource : public PacketSource {
+    SimplePacketSource()
+        : mCounter(0) {
+    }
+
+    virtual sp<ABuffer> getNextAccessUnit() {
+        sp<ABuffer> buffer = new ABuffer(4);
+        uint8_t *dst = buffer->data();
+        dst[0] = mCounter >> 24;
+        dst[1] = (mCounter >> 16) & 0xff;
+        dst[2] = (mCounter >> 8) & 0xff;
+        dst[3] = mCounter & 0xff;
+
+        buffer->meta()->setInt64("timeUs", mCounter * 1000000ll / kFrameRate);
+
+        ++mCounter;
+
+        return buffer;
+    }
+
+protected:
+    virtual ~SimplePacketSource() {
+    }
+
+private:
+    enum {
+        kFrameRate = 30
+    };
+
+    uint32_t mCounter;
+
+    DISALLOW_EVIL_CONSTRUCTORS(SimplePacketSource);
+};
+
 struct TestHandler : public AHandler {
     TestHandler(const sp<ANetworkSession> &netSession);
 
@@ -52,18 +157,39 @@
         kWhatSenderNotify,
         kWhatSendMore,
         kWhatStop,
+        kWhatTimeSyncerNotify,
     };
 
+#if 1
+    static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_UDP;
+    static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_UDP;
+#else
+    static const RTPBase::TransportMode kRTPMode = RTPBase::TRANSPORT_TCP;
+    static const RTPBase::TransportMode kRTCPMode = RTPBase::TRANSPORT_NONE;
+#endif
+
+#if 1
+    static const RTPBase::PacketizationMode kPacketizationMode
+        = RTPBase::PACKETIZATION_H264;
+#else
+    static const RTPBase::PacketizationMode kPacketizationMode
+        = RTPBase::PACKETIZATION_NONE;
+#endif
+
     sp<ANetworkSession> mNetSession;
-    sp<NuMediaExtractor> mExtractor;
+    sp<PacketSource> mSource;
     sp<RTPSender> mSender;
     sp<RTPReceiver> mReceiver;
 
-    size_t mMaxSampleSize;
+    sp<TimeSyncer> mTimeSyncer;
+    bool mTimeSyncerStarted;
 
     int64_t mFirstTimeRealUs;
     int64_t mFirstTimeMediaUs;
 
+    int64_t mTimeOffsetUs;
+    bool mTimeOffsetValid;
+
     status_t readMore();
 
     DISALLOW_EVIL_CONSTRUCTORS(TestHandler);
@@ -71,9 +197,11 @@
 
 TestHandler::TestHandler(const sp<ANetworkSession> &netSession)
     : mNetSession(netSession),
-      mMaxSampleSize(1024 * 1024),
+      mTimeSyncerStarted(false),
       mFirstTimeRealUs(-1ll),
-      mFirstTimeMediaUs(-1ll) {
+      mFirstTimeMediaUs(-1ll),
+      mTimeOffsetUs(-1ll),
+      mTimeOffsetValid(false) {
 }
 
 TestHandler::~TestHandler() {
@@ -91,23 +219,48 @@
     msg->post();
 }
 
+static void dumpDelay(int64_t delayMs) {
+    static const int64_t kMinDelayMs = 0;
+    static const int64_t kMaxDelayMs = 300;
+
+    const char *kPattern = "########################################";
+    size_t kPatternSize = strlen(kPattern);
+
+    int n = (kPatternSize * (delayMs - kMinDelayMs))
+                / (kMaxDelayMs - kMinDelayMs);
+
+    if (n < 0) {
+        n = 0;
+    } else if ((size_t)n > kPatternSize) {
+        n = kPatternSize;
+    }
+
+    ALOGI("(%4lld ms) %s\n",
+          delayMs,
+          kPattern + kPatternSize - n);
+}
+
 void TestHandler::onMessageReceived(const sp<AMessage> &msg) {
     switch (msg->what()) {
         case kWhatListen:
         {
-            sp<AMessage> notify = new AMessage(kWhatReceiverNotify, id());
-            mReceiver = new RTPReceiver(mNetSession, notify);
+            sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+            mTimeSyncer = new TimeSyncer(mNetSession, notify);
+            looper()->registerHandler(mTimeSyncer);
+
+            notify = new AMessage(kWhatReceiverNotify, id());
+            mReceiver = new RTPReceiver(
+                    mNetSession, notify, RTPReceiver::FLAG_AUTO_CONNECT);
             looper()->registerHandler(mReceiver);
 
             CHECK_EQ((status_t)OK,
-                     mReceiver->registerPacketType(
-                         33, RTPReceiver::PACKETIZATION_H264));
+                     mReceiver->registerPacketType(33, kPacketizationMode));
 
             int32_t receiverRTPPort;
             CHECK_EQ((status_t)OK,
                      mReceiver->initAsync(
-                         RTPReceiver::TRANSPORT_UDP,  // rtpMode
-                         RTPReceiver::TRANSPORT_UDP,  // rtcpMode
+                         kRTPMode,
+                         kRTCPMode,
                          &receiverRTPPort));
 
             printf("picked receiverRTPPort %d\n", receiverRTPPort);
@@ -125,33 +278,23 @@
             AString host;
             CHECK(msg->findString("host", &host));
 
+            sp<AMessage> notify = new AMessage(kWhatTimeSyncerNotify, id());
+            mTimeSyncer = new TimeSyncer(mNetSession, notify);
+            looper()->registerHandler(mTimeSyncer);
+            mTimeSyncer->startServer(8123);
+
             int32_t receiverRTPPort;
             CHECK(msg->findInt32("port", &receiverRTPPort));
 
-            mExtractor = new NuMediaExtractor;
-            CHECK_EQ((status_t)OK,
-                     mExtractor->setDataSource(
-                             "/sdcard/Frame Counter HD 30FPS_1080p.mp4"));
+#if 1
+            mSource = new MediaPacketSource;
+#else
+            mSource = new SimplePacketSource;
+#endif
 
-            bool haveVideo = false;
-            for (size_t i = 0; i < mExtractor->countTracks(); ++i) {
-                sp<AMessage> format;
-                CHECK_EQ((status_t)OK, mExtractor->getTrackFormat(i, &format));
-
-                AString mime;
-                CHECK(format->findString("mime", &mime));
-
-                if (!strcasecmp(MEDIA_MIMETYPE_VIDEO_AVC, mime.c_str())) {
-                    mExtractor->selectTrack(i);
-                    haveVideo = true;
-                    break;
-                }
-            }
-
-            CHECK(haveVideo);
-
-            sp<AMessage> notify = new AMessage(kWhatSenderNotify, id());
+            notify = new AMessage(kWhatSenderNotify, id());
             mSender = new RTPSender(mNetSession, notify);
+
             looper()->registerHandler(mSender);
 
             int32_t senderRTPPort;
@@ -159,9 +302,10 @@
                      mSender->initAsync(
                          host.c_str(),
                          receiverRTPPort,
-                         RTPSender::TRANSPORT_UDP,  // rtpMode
-                         receiverRTPPort + 1,
-                         RTPSender::TRANSPORT_UDP,  // rtcpMode
+                         kRTPMode,
+                         kRTCPMode == RTPBase::TRANSPORT_NONE
+                            ? -1 : receiverRTPPort + 1,
+                         kRTCPMode,
                          &senderRTPPort));
 
             printf("picked senderRTPPort %d\n", senderRTPPort);
@@ -201,7 +345,7 @@
 
         case kWhatReceiverNotify:
         {
-            ALOGI("kWhatReceiverNotify");
+            ALOGV("kWhatReceiverNotify");
 
             int32_t what;
             CHECK(msg->findInt32("what", &what));
@@ -216,8 +360,40 @@
                     break;
                 }
 
-                case RTPSender::kWhatError:
+                case RTPReceiver::kWhatError:
                     break;
+
+                case RTPReceiver::kWhatAccessUnit:
+                {
+#if 0
+                    if (!mTimeSyncerStarted) {
+                        mTimeSyncer->startClient("172.18.41.216", 8123);
+                        mTimeSyncerStarted = true;
+                    }
+
+                    sp<ABuffer> accessUnit;
+                    CHECK(msg->findBuffer("accessUnit", &accessUnit));
+
+                    int64_t timeUs;
+                    CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
+
+                    if (mTimeOffsetValid) {
+                        timeUs -= mTimeOffsetUs;
+                        int64_t nowUs = ALooper::GetNowUs();
+                        int64_t delayMs = (nowUs - timeUs) / 1000ll;
+
+                        dumpDelay(delayMs);
+                    }
+#endif
+                    break;
+                }
+
+                case RTPReceiver::kWhatPacketLost:
+                    ALOGV("kWhatPacketLost");
+                    break;
+
+                default:
+                    TRESPASS();
             }
             break;
         }
@@ -231,7 +407,7 @@
                      mSender->queueBuffer(
                          accessUnit,
                          33,
-                         RTPSender::PACKETIZATION_H264));
+                         kPacketizationMode));
 
             status_t err = readMore();
 
@@ -253,31 +429,33 @@
                 mSender.clear();
             }
 
-            mExtractor.clear();
+            mSource.clear();
 
             looper()->stop();
             break;
         }
 
+        case kWhatTimeSyncerNotify:
+        {
+            CHECK(msg->findInt64("offset", &mTimeOffsetUs));
+            mTimeOffsetValid = true;
+            break;
+        }
+
         default:
             TRESPASS();
     }
 }
 
 status_t TestHandler::readMore() {
-    int64_t timeUs;
-    status_t err = mExtractor->getSampleTime(&timeUs);
+    sp<ABuffer> accessUnit = mSource->getNextAccessUnit();
 
-    if (err != OK) {
-        return err;
+    if (accessUnit == NULL) {
+        return ERROR_END_OF_STREAM;
     }
 
-    sp<ABuffer> accessUnit = new ABuffer(mMaxSampleSize);
-    CHECK_EQ((status_t)OK, mExtractor->readSampleData(accessUnit));
-
-    accessUnit->meta()->setInt64("timeUs", timeUs);
-
-    CHECK_EQ((status_t)OK, mExtractor->advance());
+    int64_t timeUs;
+    CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
 
     int64_t nowUs = ALooper::GetNowUs();
     int64_t whenUs;
@@ -289,6 +467,8 @@
         whenUs = mFirstTimeRealUs + timeUs - mFirstTimeMediaUs;
     }
 
+    accessUnit->meta()->setInt64("timeUs", whenUs);
+
     sp<AMessage> msg = new AMessage(kWhatSendMore, id());
     msg->setBuffer("accessUnit", accessUnit);
     msg->post(whenUs - nowUs);
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
index 1a08bf5..5db2099 100644
--- a/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.cpp
@@ -57,7 +57,8 @@
       mSetupDeferred(false),
       mLatencyCount(0),
       mLatencySumUs(0ll),
-      mLatencyMaxUs(0ll) {
+      mLatencyMaxUs(0ll),
+      mMaxDelayMs(-1ll) {
     // We support any and all resolutions, but prefer 720p30
     mSinkSupportedVideoFormats.setNativeResolution(
             VideoFormats::RESOLUTION_CEA, 5);  // 1280 x 720 p30
@@ -296,9 +297,13 @@
     }
 }
 
-static void dumpDelay(size_t trackIndex, int64_t timeUs) {
+void WifiDisplaySink::dumpDelay(size_t trackIndex, int64_t timeUs) {
     int64_t delayMs = (ALooper::GetNowUs() - timeUs) / 1000ll;
 
+    if (delayMs > mMaxDelayMs) {
+        mMaxDelayMs = delayMs;
+    }
+
     static const int64_t kMinDelayMs = 0;
     static const int64_t kMaxDelayMs = 300;
 
@@ -314,9 +319,10 @@
         n = kPatternSize;
     }
 
-    ALOGI("[%lld]: (%4lld ms) %s",
+    ALOGI("[%lld]: (%4lld ms / %4lld ms) %s",
           timeUs / 1000,
           delayMs,
+          mMaxDelayMs,
           kPattern + kPatternSize - n);
 }
 
@@ -350,14 +356,19 @@
                 looper()->registerHandler(mRenderer);
             }
 
-            CHECK(mTimeOffsetValid);
-
             sp<ABuffer> accessUnit;
             CHECK(msg->findBuffer("accessUnit", &accessUnit));
 
             int64_t timeUs;
             CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
 
+            if (!mTimeOffsetValid && !(mFlags & FLAG_SPECIAL_MODE)) {
+                mTimeOffsetUs = timeUs - ALooper::GetNowUs();
+                mTimeOffsetValid = true;
+            }
+
+            CHECK(mTimeOffsetValid);
+
             // We are the timesync _client_,
             // client time = server time - time offset.
             timeUs -= mTimeOffsetUs;
diff --git a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
index 7c62057..adb9d89 100644
--- a/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
+++ b/media/libstagefright/wifi-display/sink/WifiDisplaySink.h
@@ -132,6 +132,8 @@
     int64_t mLatencySumUs;
     int64_t mLatencyMaxUs;
 
+    int64_t mMaxDelayMs;
+
     status_t sendM2(int32_t sessionID);
     status_t sendSetup(int32_t sessionID, const char *uri);
     status_t sendPlay(int32_t sessionID, const char *uri);
@@ -184,6 +186,8 @@
             const char *url, AString *host, int32_t *port, AString *path,
             AString *user, AString *pass);
 
+    void dumpDelay(size_t trackIndex, int64_t timeUs);
+
     DISALLOW_EVIL_CONSTRUCTORS(WifiDisplaySink);
 };
 
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index 715d0b5..cacfcca 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -709,8 +709,11 @@
                 Converter::GetInt32Property(
                         "media.wfd.video-framerate", -1);
 
-            if (rateHz < 0.0) {
-                rateHz = repeaterSource->getFrameRate();
+            char val[PROPERTY_VALUE_MAX];
+            if (rateHz < 0.0
+                    && property_get("media.wfd.video-framerate", val, NULL)
+                    && !strcasecmp("adaptive", val)) {
+                 rateHz = repeaterSource->getFrameRate();
 
                 if (avgLatencyUs > 300000ll) {
                     rateHz *= 0.9;
diff --git a/services/audioflinger/Threads.cpp b/services/audioflinger/Threads.cpp
index 9d98f0b..47ca100 100644
--- a/services/audioflinger/Threads.cpp
+++ b/services/audioflinger/Threads.cpp
@@ -3122,16 +3122,15 @@
     Vector< sp<Track> > *tracksToRemove
 )
 {
-    sp<Track> trackToRemove;
-
+    size_t count = mActiveTracks.size();
     mixer_state mixerStatus = MIXER_IDLE;
 
     // find out which tracks need to be processed
-    if (mActiveTracks.size() != 0) {
-        sp<Track> t = mActiveTracks[0].promote();
+    for (size_t i = 0; i < count; i++) {
+        sp<Track> t = mActiveTracks[i].promote();
         // The track died recently
         if (t == 0) {
-            return MIXER_IDLE;
+            continue;
         }
 
         Track* const track = t.get();
@@ -3180,35 +3179,40 @@
                 }
                 right = v_clamped/MAX_GAIN;
             }
+            // Only consider last track started for volume and mixer state control.
+            // This is the last entry in mActiveTracks unless a track underruns.
+            // As we only care about the transition phase between two tracks on a
+            // direct output, it is not a problem to ignore the underrun case.
+            if (i == (count - 1)) {
+                if (left != mLeftVolFloat || right != mRightVolFloat) {
+                    mLeftVolFloat = left;
+                    mRightVolFloat = right;
 
-            if (left != mLeftVolFloat || right != mRightVolFloat) {
-                mLeftVolFloat = left;
-                mRightVolFloat = right;
+                    // Convert volumes from float to 8.24
+                    uint32_t vl = (uint32_t)(left * (1 << 24));
+                    uint32_t vr = (uint32_t)(right * (1 << 24));
 
-                // Convert volumes from float to 8.24
-                uint32_t vl = (uint32_t)(left * (1 << 24));
-                uint32_t vr = (uint32_t)(right * (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()) {
-                    // Do not ramp volume if volume is controlled by effect
-                    mEffectChains[0]->setVolume_l(&vl, &vr);
-                    left = (float)vl / (1 << 24);
-                    right = (float)vr / (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()) {
+                        // Do not ramp volume if volume is controlled by effect
+                        mEffectChains[0]->setVolume_l(&vl, &vr);
+                        left = (float)vl / (1 << 24);
+                        right = (float)vr / (1 << 24);
+                    }
+                    mOutput->stream->set_volume(mOutput->stream, left, right);
                 }
-                mOutput->stream->set_volume(mOutput->stream, left, right);
-            }
 
-            // reset retry count
-            track->mRetryCount = kMaxTrackRetriesDirect;
-            mActiveTrack = t;
-            mixerStatus = MIXER_TRACKS_READY;
+                // reset retry count
+                track->mRetryCount = kMaxTrackRetriesDirect;
+                mActiveTrack = t;
+                mixerStatus = MIXER_TRACKS_READY;
+            }
         } else {
-            // clear effect chain input buffer if an active track underruns to avoid sending
-            // previous audio buffer again to effects
-            if (!mEffectChains.isEmpty()) {
+            // clear effect chain input buffer if the last active track started underruns
+            // to avoid sending previous audio buffer again to effects
+            if (!mEffectChains.isEmpty() && (i == (count -1))) {
                 mEffectChains[0]->clearInputBuffer();
             }
 
@@ -3224,33 +3228,36 @@
                     if (track->isStopped()) {
                         track->reset();
                     }
-                    trackToRemove = track;
+                    tracksToRemove->add(track);
                 }
             } else {
                 // No buffers for this track. Give it a few chances to
                 // fill a buffer, then remove it from active list.
+                // Only consider last track started for mixer state control
                 if (--(track->mRetryCount) <= 0) {
                     ALOGV("BUFFER TIMEOUT: remove(%d) from active list", track->name());
-                    trackToRemove = track;
-                } else {
+                    tracksToRemove->add(track);
+                } else if (i == (count -1)){
                     mixerStatus = MIXER_TRACKS_ENABLED;
                 }
             }
         }
     }
 
-    // FIXME merge this with similar code for removing multiple tracks
     // remove all the tracks that need to be...
-    if (CC_UNLIKELY(trackToRemove != 0)) {
-        tracksToRemove->add(trackToRemove);
-        mActiveTracks.remove(trackToRemove);
-        if (!mEffectChains.isEmpty()) {
-            ALOGV("stopping track on chain %p for session Id: %d", mEffectChains[0].get(),
-                    trackToRemove->sessionId());
-            mEffectChains[0]->decActiveTrackCnt();
-        }
-        if (trackToRemove->isTerminated()) {
-            removeTrack_l(trackToRemove);
+    count = tracksToRemove->size();
+    if (CC_UNLIKELY(count)) {
+        for (size_t i = 0 ; i < count ; i++) {
+            const sp<Track>& track = tracksToRemove->itemAt(i);
+            mActiveTracks.remove(track);
+            if (!mEffectChains.isEmpty()) {
+                ALOGV("stopping track on chain %p for session Id: %d", mEffectChains[0].get(),
+                      track->sessionId());
+                mEffectChains[0]->decActiveTrackCnt();
+            }
+            if (track->isTerminated()) {
+                removeTrack_l(track);
+            }
         }
     }
 
diff --git a/services/camera/libcameraservice/Camera3Device.cpp b/services/camera/libcameraservice/Camera3Device.cpp
index f2c8c04..e53dbb5 100644
--- a/services/camera/libcameraservice/Camera3Device.cpp
+++ b/services/camera/libcameraservice/Camera3Device.cpp
@@ -262,6 +262,8 @@
     ATRACE_CALL();
     Mutex::Autolock l(mLock);
 
+    // TODO: take ownership of the request
+
     switch (mStatus) {
         case STATUS_ERROR:
             ALOGE("%s: Device has encountered a serious error", __FUNCTION__);
@@ -363,10 +365,8 @@
 
 status_t Camera3Device::waitUntilRequestReceived(int32_t requestId, nsecs_t timeout) {
     ATRACE_CALL();
-    (void)requestId; (void)timeout;
 
-    ALOGE("%s: Unimplemented", __FUNCTION__);
-    return INVALID_OPERATION;
+    return mRequestThread->waitUntilRequestProcessed(requestId, timeout);
 }
 
 status_t Camera3Device::createStream(sp<ANativeWindow> consumer,
@@ -698,28 +698,62 @@
 
 status_t Camera3Device::triggerAutofocus(uint32_t id) {
     ATRACE_CALL();
-    (void)id;
 
-    ALOGE("%s: Unimplemented", __FUNCTION__);
-    return INVALID_OPERATION;
+    ALOGV("%s: Triggering autofocus, id %d", __FUNCTION__, id);
+    // Mix-in this trigger into the next request and only the next request.
+    RequestTrigger trigger[] = {
+        {
+            ANDROID_CONTROL_AF_TRIGGER,
+            ANDROID_CONTROL_AF_TRIGGER_START
+        },
+        {
+            ANDROID_CONTROL_AF_TRIGGER_ID,
+            static_cast<int32_t>(id)
+        },
+    };
+
+    return mRequestThread->queueTrigger(trigger,
+                                        sizeof(trigger)/sizeof(trigger[0]));
 }
 
 status_t Camera3Device::triggerCancelAutofocus(uint32_t id) {
     ATRACE_CALL();
-    (void)id;
 
-    ALOGE("%s: Unimplemented", __FUNCTION__);
-    return INVALID_OPERATION;
+    ALOGV("%s: Triggering cancel autofocus, id %d", __FUNCTION__, id);
+    // Mix-in this trigger into the next request and only the next request.
+    RequestTrigger trigger[] = {
+        {
+            ANDROID_CONTROL_AF_TRIGGER,
+            ANDROID_CONTROL_AF_TRIGGER_CANCEL
+        },
+        {
+            ANDROID_CONTROL_AF_TRIGGER_ID,
+            static_cast<int32_t>(id)
+        },
+    };
 
+    return mRequestThread->queueTrigger(trigger,
+                                        sizeof(trigger)/sizeof(trigger[0]));
 }
 
 status_t Camera3Device::triggerPrecaptureMetering(uint32_t id) {
     ATRACE_CALL();
-    (void)id;
 
-    ALOGE("%s: Unimplemented", __FUNCTION__);
-    return INVALID_OPERATION;
+    ALOGV("%s: Triggering precapture metering, id %d", __FUNCTION__, id);
+    // Mix-in this trigger into the next request and only the next request.
+    RequestTrigger trigger[] = {
+        {
+            ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER,
+            ANDROID_CONTROL_AE_PRECAPTURE_TRIGGER_START
+        },
+        {
+            ANDROID_CONTROL_AE_PRECAPTURE_ID,
+            static_cast<int32_t>(id)
+        },
+    };
 
+    return mRequestThread->queueTrigger(trigger,
+                                        sizeof(trigger)/sizeof(trigger[0]));
 }
 
 status_t Camera3Device::pushReprocessBuffer(int reprocessStreamId,
@@ -997,9 +1031,13 @@
     // Dispatch any 3A change events to listeners
     if (listener != NULL) {
         if (new3aState.aeState != cur3aState.aeState) {
+            ALOGVV("%s: AE state changed from 0x%x to 0x%x",
+                   __FUNCTION__, cur3aState.aeState, new3aState.aeState);
             listener->notifyAutoExposure(new3aState.aeState, aeTriggerId);
         }
         if (new3aState.afState != cur3aState.afState) {
+            ALOGVV("%s: AF state changed from 0x%x to 0x%x",
+                   __FUNCTION__, cur3aState.afState, new3aState.afState);
             listener->notifyAutoFocus(new3aState.afState, afTriggerId);
         }
         if (new3aState.awbState != cur3aState.awbState) {
@@ -1059,7 +1097,8 @@
         mReconfigured(false),
         mDoPause(false),
         mPaused(true),
-        mFrameNumber(0) {
+        mFrameNumber(0),
+        mLatestRequestId(NAME_NOT_FOUND) {
 }
 
 void Camera3Device::RequestThread::configurationComplete() {
@@ -1075,6 +1114,57 @@
     return OK;
 }
 
+
+status_t Camera3Device::RequestThread::queueTrigger(
+        RequestTrigger trigger[],
+        size_t count) {
+
+    Mutex::Autolock l(mTriggerMutex);
+    status_t ret;
+
+    for (size_t i = 0; i < count; ++i) {
+        ret = queueTriggerLocked(trigger[i]);
+
+        if (ret != OK) {
+            return ret;
+        }
+    }
+
+    return OK;
+}
+
+status_t Camera3Device::RequestThread::queueTriggerLocked(
+        RequestTrigger trigger) {
+
+    uint32_t tag = trigger.metadataTag;
+    ssize_t index = mTriggerMap.indexOfKey(tag);
+
+    switch (trigger.getTagType()) {
+        case TYPE_BYTE:
+        // fall-through
+        case TYPE_INT32:
+            break;
+        default:
+            ALOGE("%s: Type not supported: 0x%x",
+                  __FUNCTION__,
+                  trigger.getTagType());
+            return INVALID_OPERATION;
+    }
+
+    /**
+     * Collect only the latest trigger, since we only have 1 field
+     * in the request settings per trigger tag, and can't send more than 1
+     * trigger per request.
+     */
+    if (index != NAME_NOT_FOUND) {
+        mTriggerMap.editValueAt(index) = trigger;
+    } else {
+        mTriggerMap.add(tag, trigger);
+    }
+
+    return OK;
+}
+
 status_t Camera3Device::RequestThread::setRepeatingRequests(
         const RequestList &requests) {
     Mutex::Autolock l(mRequestLock);
@@ -1108,6 +1198,24 @@
     return OK;
 }
 
+status_t Camera3Device::RequestThread::waitUntilRequestProcessed(
+        int32_t requestId, nsecs_t timeout) {
+    Mutex::Autolock l(mLatestRequestMutex);
+    status_t res;
+    while (mLatestRequestId != requestId) {
+        nsecs_t startTime = systemTime();
+
+        res = mLatestRequestSignal.waitRelative(mLatestRequestMutex, timeout);
+        if (res != OK) return res;
+
+        timeout -= (systemTime() - startTime);
+    }
+
+    return OK;
+}
+
+
+
 bool Camera3Device::RequestThread::threadLoop() {
 
     status_t res;
@@ -1125,16 +1233,55 @@
     }
 
     // Create request to HAL
-
     camera3_capture_request_t request = camera3_capture_request_t();
+    Vector<camera3_stream_buffer_t> outputBuffers;
 
-    if (mPrevRequest != nextRequest) {
+    // Insert any queued triggers (before metadata is locked)
+    int32_t triggerCount;
+    res = insertTriggers(nextRequest);
+    if (res < 0) {
+        ALOGE("RequestThread: Unable to insert triggers "
+              "(capture request %d, HAL device: %s (%d)",
+              (mFrameNumber+1), strerror(-res), res);
+        cleanUpFailedRequest(request, nextRequest, outputBuffers);
+        return false;
+    }
+    triggerCount = res;
+
+    bool triggersMixedIn = (triggerCount > 0 || mPrevTriggers > 0);
+
+    // If the request is the same as last, or we had triggers last time
+    if (mPrevRequest != nextRequest || triggersMixedIn) {
+        /**
+         * The request should be presorted so accesses in HAL
+         *   are O(logn). Sidenote, sorting a sorted metadata is nop.
+         */
+        nextRequest->mSettings.sort();
         request.settings = nextRequest->mSettings.getAndLock();
         mPrevRequest = nextRequest;
-    } // else leave request.settings NULL to indicate 'reuse latest given'
+        ALOGVV("%s: Request settings are NEW", __FUNCTION__);
+
+        IF_ALOGV() {
+            camera_metadata_ro_entry_t e = camera_metadata_ro_entry_t();
+            find_camera_metadata_ro_entry(
+                    request.settings,
+                    ANDROID_CONTROL_AF_TRIGGER,
+                    &e
+            );
+            if (e.count > 0) {
+                ALOGV("%s: Request (frame num %d) had AF trigger 0x%x",
+                      __FUNCTION__,
+                      mFrameNumber+1,
+                      e.data.u8[0]);
+            }
+        }
+    } else {
+        // leave request.settings NULL to indicate 'reuse latest given'
+        ALOGVV("%s: Request settings are REUSED",
+               __FUNCTION__);
+    }
 
     camera3_stream_buffer_t inputBuffer;
-    Vector<camera3_stream_buffer_t> outputBuffers;
 
     // Fill in buffers
 
@@ -1168,6 +1315,7 @@
 
     request.frame_number = mFrameNumber++;
 
+
     // Submit request and block until ready for next one
 
     res = mHal3Device->ops->process_capture_request(mHal3Device, &request);
@@ -1181,6 +1329,35 @@
     if (request.settings != NULL) {
         nextRequest->mSettings.unlock(request.settings);
     }
+
+    // Remove any previously queued triggers (after unlock)
+    res = removeTriggers(mPrevRequest);
+    if (res != OK) {
+        ALOGE("RequestThread: Unable to remove triggers "
+              "(capture request %d, HAL device: %s (%d)",
+              request.frame_number, strerror(-res), res);
+        return false;
+    }
+    mPrevTriggers = triggerCount;
+
+    // Read android.request.id from the request settings metadata
+    // - inform waitUntilRequestProcessed thread of a new request ID
+    {
+        Mutex::Autolock al(mLatestRequestMutex);
+
+        camera_metadata_entry_t requestIdEntry =
+                nextRequest->mSettings.find(ANDROID_REQUEST_ID);
+        if (requestIdEntry.count > 0) {
+            mLatestRequestId = requestIdEntry.data.i32[0];
+        } else {
+            ALOGW("%s: Did not have android.request.id set in the request",
+                  __FUNCTION__);
+            mLatestRequestId = NAME_NOT_FOUND;
+        }
+
+        mLatestRequestSignal.signal();
+    }
+
     return true;
 }
 
@@ -1285,6 +1462,141 @@
     return false;
 }
 
+status_t Camera3Device::RequestThread::insertTriggers(
+        const sp<CaptureRequest> &request) {
+
+    Mutex::Autolock al(mTriggerMutex);
+
+    CameraMetadata &metadata = request->mSettings;
+    size_t count = mTriggerMap.size();
+
+    for (size_t i = 0; i < count; ++i) {
+        RequestTrigger trigger = mTriggerMap.valueAt(i);
+
+        uint32_t tag = trigger.metadataTag;
+        camera_metadata_entry entry = metadata.find(tag);
+
+        if (entry.count > 0) {
+            /**
+             * Already has an entry for this trigger in the request.
+             * Rewrite it with our requested trigger value.
+             */
+            RequestTrigger oldTrigger = trigger;
+
+            oldTrigger.entryValue = entry.data.u8[0];
+
+            mTriggerReplacedMap.add(tag, oldTrigger);
+        } else {
+            /**
+             * More typical, no trigger entry, so we just add it
+             */
+            mTriggerRemovedMap.add(tag, trigger);
+        }
+
+        status_t res;
+
+        switch (trigger.getTagType()) {
+            case TYPE_BYTE: {
+                uint8_t entryValue = static_cast<uint8_t>(trigger.entryValue);
+                res = metadata.update(tag,
+                                      &entryValue,
+                                      /*count*/1);
+                break;
+            }
+            case TYPE_INT32:
+                res = metadata.update(tag,
+                                      &trigger.entryValue,
+                                      /*count*/1);
+                break;
+            default:
+                ALOGE("%s: Type not supported: 0x%x",
+                      __FUNCTION__,
+                      trigger.getTagType());
+                return INVALID_OPERATION;
+        }
+
+        if (res != OK) {
+            ALOGE("%s: Failed to update request metadata with trigger tag %s"
+                  ", value %d", __FUNCTION__, trigger.getTagName(),
+                  trigger.entryValue);
+            return res;
+        }
+
+        ALOGV("%s: Mixed in trigger %s, value %d", __FUNCTION__,
+              trigger.getTagName(),
+              trigger.entryValue);
+    }
+
+    mTriggerMap.clear();
+
+    return count;
+}
+
+status_t Camera3Device::RequestThread::removeTriggers(
+        const sp<CaptureRequest> &request) {
+    Mutex::Autolock al(mTriggerMutex);
+
+    CameraMetadata &metadata = request->mSettings;
+
+    /**
+     * Replace all old entries with their old values.
+     */
+    for (size_t i = 0; i < mTriggerReplacedMap.size(); ++i) {
+        RequestTrigger trigger = mTriggerReplacedMap.valueAt(i);
+
+        status_t res;
+
+        uint32_t tag = trigger.metadataTag;
+        switch (trigger.getTagType()) {
+            case TYPE_BYTE: {
+                uint8_t entryValue = static_cast<uint8_t>(trigger.entryValue);
+                res = metadata.update(tag,
+                                      &entryValue,
+                                      /*count*/1);
+                break;
+            }
+            case TYPE_INT32:
+                res = metadata.update(tag,
+                                      &trigger.entryValue,
+                                      /*count*/1);
+                break;
+            default:
+                ALOGE("%s: Type not supported: 0x%x",
+                      __FUNCTION__,
+                      trigger.getTagType());
+                return INVALID_OPERATION;
+        }
+
+        if (res != OK) {
+            ALOGE("%s: Failed to restore request metadata with trigger tag %s"
+                  ", trigger value %d", __FUNCTION__,
+                  trigger.getTagName(), trigger.entryValue);
+            return res;
+        }
+    }
+    mTriggerReplacedMap.clear();
+
+    /**
+     * Remove all new entries.
+     */
+    for (size_t i = 0; i < mTriggerRemovedMap.size(); ++i) {
+        RequestTrigger trigger = mTriggerRemovedMap.valueAt(i);
+        status_t res = metadata.erase(trigger.metadataTag);
+
+        if (res != OK) {
+            ALOGE("%s: Failed to erase metadata with trigger tag %s"
+                  ", trigger value %d", __FUNCTION__,
+                  trigger.getTagName(), trigger.entryValue);
+            return res;
+        }
+    }
+    mTriggerRemovedMap.clear();
+
+    return OK;
+}
+
+
+
 /**
  * Static callback forwarding methods from HAL to instance
  */
diff --git a/services/camera/libcameraservice/Camera3Device.h b/services/camera/libcameraservice/Camera3Device.h
index 8600c6c..7f294e6 100644
--- a/services/camera/libcameraservice/Camera3Device.h
+++ b/services/camera/libcameraservice/Camera3Device.h
@@ -109,7 +109,7 @@
 
   private:
     static const nsecs_t       kShutdownTimeout = 5000000000; // 5 sec
-
+    struct                     RequestTrigger;
 
     Mutex                      mLock;
 
@@ -172,6 +172,23 @@
      */
     status_t           configureStreamsLocked();
 
+    struct RequestTrigger {
+        // Metadata tag number, e.g. android.control.aePrecaptureTrigger
+        uint32_t metadataTag;
+        // Metadata value, e.g. 'START' or the trigger ID
+        int32_t entryValue;
+
+        // The last part of the fully qualified path, e.g. afTrigger
+        const char *getTagName() const {
+            return get_camera_metadata_tag_name(metadataTag) ?: "NULL";
+        }
+
+        // e.g. TYPE_BYTE, TYPE_INT32, etc.
+        int getTagType() const {
+            return get_camera_metadata_tag_type(metadataTag);
+        }
+    };
+
     /**
      * Thread for managing capture request submission to HAL device.
      */
@@ -198,6 +215,14 @@
         status_t queueRequest(sp<CaptureRequest> request);
 
         /**
+         * Queue a trigger to be dispatched with the next outgoing
+         * process_capture_request. The settings for that request only
+         * will be temporarily rewritten to add the trigger tag/value.
+         * Subsequent requests will not be rewritten (for this tag).
+         */
+        status_t queueTrigger(RequestTrigger trigger[], size_t count);
+
+        /**
          * Pause/unpause the capture thread. Doesn't block, so use
          * waitUntilPaused to wait until the thread is paused.
          */
@@ -210,11 +235,27 @@
          */
         status_t waitUntilPaused(nsecs_t timeout);
 
+        /**
+         * Wait until thread processes the capture request with settings'
+         * android.request.id == requestId.
+         *
+         * Returns TIMED_OUT in case the thread does not process the request
+         * within the timeout.
+         */
+        status_t waitUntilRequestProcessed(int32_t requestId, nsecs_t timeout);
+
       protected:
 
         virtual bool threadLoop();
 
       private:
+        status_t           queueTriggerLocked(RequestTrigger trigger);
+        // Mix-in queued triggers into this request
+        int32_t            insertTriggers(const sp<CaptureRequest> &request);
+        // Purge the queued triggers from this request,
+        //  restoring the old field values for those tags.
+        status_t           removeTriggers(const sp<CaptureRequest> &request);
+
         static const nsecs_t kRequestTimeout = 50e6; // 50 ms
 
         // Waits for a request, or returns NULL if times out.
@@ -249,8 +290,20 @@
         Condition          mPausedSignal;
 
         sp<CaptureRequest> mPrevRequest;
+        int32_t            mPrevTriggers;
 
         int32_t            mFrameNumber;
+
+        Mutex              mLatestRequestMutex;
+        Condition          mLatestRequestSignal;
+        // android.request.id for latest process_capture_request
+        int32_t            mLatestRequestId;
+
+        typedef KeyedVector<uint32_t/*tag*/, RequestTrigger> TriggerMap;
+        Mutex              mTriggerMutex;
+        TriggerMap         mTriggerMap;
+        TriggerMap         mTriggerRemovedMap;
+        TriggerMap         mTriggerReplacedMap;
     };
     sp<RequestThread> mRequestThread;
 
diff --git a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
index 1880912..ee03329 100644
--- a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
+++ b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
@@ -270,6 +270,9 @@
         processor->clearZslQueue();
     }
 
+    /**
+     * Fire the jpegCallback in Camera#takePicture(..., jpegCallback)
+     */
     if (mCaptureBuffer != 0 && res == OK) {
         Camera2Client::SharedCameraCallbacks::Lock
             l(client->mSharedCameraCallbacks);
@@ -367,6 +370,8 @@
 CaptureSequencer::CaptureState CaptureSequencer::manageStandardStart(
         sp<Camera2Client> &client) {
     ATRACE_CALL();
+
+    // Get the onFrameAvailable callback when the requestID == mCaptureId
     client->registerFrameListener(mCaptureId, mCaptureId + 1,
             this);
     {
@@ -426,6 +431,13 @@
     SharedParameters::Lock l(client->getParameters());
     Vector<uint8_t> outputStreams;
 
+    /**
+     * Set up output streams in the request
+     *  - preview
+     *  - capture/jpeg
+     *  - callback (if preview callbacks enabled)
+     *  - recording (if recording enabled)
+     */
     outputStreams.push(client->getPreviewStreamId());
     outputStreams.push(client->getCaptureStreamId());
 
@@ -454,6 +466,7 @@
         return DONE;
     }
 
+    // Create a capture copy since CameraDeviceBase#capture takes ownership
     CameraMetadata captureCopy = mCaptureRequest;
     if (captureCopy.entryCount() == 0) {
         ALOGE("%s: Camera %d: Unable to copy capture request for HAL device",
@@ -461,7 +474,12 @@
         return DONE;
     }
 
+    /**
+     * Clear the streaming request for still-capture pictures
+     *   (as opposed to i.e. video snapshots)
+     */
     if (l.mParameters.state == Parameters::STILL_CAPTURE) {
+        // API definition of takePicture() - stop preview before taking pic
         res = client->stopStream();
         if (res != OK) {
             ALOGE("%s: Camera %d: Unable to stop preview for still capture: "
@@ -488,6 +506,8 @@
     status_t res;
     ATRACE_CALL();
     Mutex::Autolock l(mInputMutex);
+
+    // Wait for new metadata result (mNewFrame)
     while (!mNewFrameReceived) {
         res = mNewFrameSignal.waitRelative(mInputMutex, kWaitDuration);
         if (res == TIMED_OUT) {
@@ -495,12 +515,17 @@
             break;
         }
     }
+
+    // Approximation of the shutter being closed
+    // - TODO: use the hal3 exposure callback in Camera3Device instead
     if (mNewFrameReceived && !mShutterNotified) {
         SharedParameters::Lock l(client->getParameters());
         /* warning: this also locks a SharedCameraCallbacks */
         shutterNotifyLocked(l.mParameters, client, mMsgType);
         mShutterNotified = true;
     }
+
+    // Wait until jpeg was captured by JpegProcessor
     while (mNewFrameReceived && !mNewCaptureReceived) {
         res = mNewCaptureSignal.waitRelative(mInputMutex, kWaitDuration);
         if (res == TIMED_OUT) {
@@ -524,7 +549,9 @@
         }
         if (entry.data.i64[0] != mCaptureTimestamp) {
             ALOGW("Mismatched capture timestamps: Metadata frame %lld,"
-                    " captured buffer %lld", entry.data.i64[0], mCaptureTimestamp);
+                    " captured buffer %lld",
+                    entry.data.i64[0],
+                    mCaptureTimestamp);
         }
         client->removeFrameListener(mCaptureId, mCaptureId + 1, this);
 
diff --git a/services/camera/libcameraservice/camera2/JpegProcessor.h b/services/camera/libcameraservice/camera2/JpegProcessor.h
index 2283f28..74f4738 100644
--- a/services/camera/libcameraservice/camera2/JpegProcessor.h
+++ b/services/camera/libcameraservice/camera2/JpegProcessor.h
@@ -44,6 +44,7 @@
     JpegProcessor(wp<Camera2Client> client, wp<CaptureSequencer> sequencer);
     ~JpegProcessor();
 
+    // CpuConsumer listener implementation
     void onFrameAvailable();
 
     status_t updateStream(const Parameters &params);