Merge "Camera: Add hotplug support (for fixed # of cameras)" into jb-mr2-dev
diff --git a/camera/ProCamera.cpp b/camera/ProCamera.cpp
index 396b009..fec5461 100644
--- a/camera/ProCamera.cpp
+++ b/camera/ProCamera.cpp
@@ -103,7 +103,7 @@
     {
         Mutex::Autolock al(mWaitMutex);
         mMetadataReady = true;
-        mLatestMetadata = tmp;
+        mLatestMetadata = tmp; // make copy
         mWaitCondition.broadcast();
     }
 
@@ -312,8 +312,6 @@
     sp<ProCameraListener> listener = mListener;
     StreamInfo& stream = getStreamInfo(streamId);
 
-    CpuConsumer::LockedBuffer buf;
-
     if (listener.get() != NULL) {
         listener->onFrameAvailable(streamId, stream.cpuConsumer);
     }
@@ -421,7 +419,7 @@
 
     // Destructive: Subsequent calls return empty metadatas
     CameraMetadata tmp = mLatestMetadata;
-    mLatestMetadata.release();
+    mLatestMetadata.clear();
 
     return tmp;
 }
diff --git a/camera/tests/ProCameraTests.cpp b/camera/tests/ProCameraTests.cpp
index 5f8f772..2b5f3ad 100644
--- a/camera/tests/ProCameraTests.cpp
+++ b/camera/tests/ProCameraTests.cpp
@@ -1029,6 +1029,9 @@
     // Consume two frames simultaneously. Unsynchronized by timestamps.
     for (int i = 0; i < REQUEST_COUNT; ++i) {
 
+        // Exhaust event queue so it doesn't keep growing
+        while (mListener->ReadEvent() != UNKNOWN);
+
         // Get the metadata
         EXPECT_OK(mCamera->waitForFrameMetadata());
         CameraMetadata meta = mCamera->consumeFrameMetadata();
diff --git a/include/media/ICrypto.h b/include/media/ICrypto.h
index 61059bd..9dcb8d9 100644
--- a/include/media/ICrypto.h
+++ b/include/media/ICrypto.h
@@ -31,7 +31,7 @@
 
     virtual status_t initCheck() const = 0;
 
-    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) const = 0;
+    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) = 0;
 
     virtual status_t createPlugin(
             const uint8_t uuid[16], const void *data, size_t size) = 0;
diff --git a/include/media/stagefright/Utils.h b/include/media/stagefright/Utils.h
index 8213af9..73940d3 100644
--- a/include/media/stagefright/Utils.h
+++ b/include/media/stagefright/Utils.h
@@ -18,6 +18,7 @@
 
 #define UTILS_H_
 
+#include <media/stagefright/foundation/AString.h>
 #include <stdint.h>
 #include <utils/Errors.h>
 #include <utils/RefBase.h>
@@ -45,6 +46,8 @@
 void convertMessageToMetaData(
         const sp<AMessage> &format, sp<MetaData> &meta);
 
+AString MakeUserAgent();
+
 }  // namespace android
 
 #endif  // UTILS_H_
diff --git a/media/libmedia/ICrypto.cpp b/media/libmedia/ICrypto.cpp
index 2defc2d..98b183a 100644
--- a/media/libmedia/ICrypto.cpp
+++ b/media/libmedia/ICrypto.cpp
@@ -48,7 +48,7 @@
         return reply.readInt32();
     }
 
-    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) const {
+    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) {
         Parcel data, reply;
         data.writeInterfaceToken(ICrypto::getInterfaceDescriptor());
         data.write(uuid, 16);
diff --git a/media/libmedia/ToneGenerator.cpp b/media/libmedia/ToneGenerator.cpp
index 42584fe..58d495e 100644
--- a/media/libmedia/ToneGenerator.cpp
+++ b/media/libmedia/ToneGenerator.cpp
@@ -803,6 +803,7 @@
     ALOGV("ToneGenerator constructor: streamType=%d, volume=%f", streamType, volume);
 
     mState = TONE_IDLE;
+    mpAudioTrack = NULL;
 
     if (AudioSystem::getOutputSamplingRate(&mSamplingRate, streamType) != NO_ERROR) {
         ALOGE("Unable to marshal AudioFlinger");
@@ -811,7 +812,6 @@
     mThreadCanCallJava = threadCanCallJava;
     mStreamType = streamType;
     mVolume = volume;
-    mpAudioTrack = NULL;
     mpToneDesc = NULL;
     mpNewToneDesc = NULL;
     // Generate tone by chunks of 20 ms to keep cadencing precision
@@ -885,6 +885,11 @@
     if ((toneType < 0) || (toneType >= NUM_TONES))
         return lResult;
 
+    toneType = getToneForRegion(toneType);
+    if (toneType == TONE_CDMA_SIGNAL_OFF) {
+        return true;
+    }
+
     if (mState == TONE_IDLE) {
         ALOGV("startTone: try to re-init AudioTrack");
         if (!initAudioTrack()) {
@@ -897,7 +902,6 @@
     mLock.lock();
 
     // Get descriptor for requested tone
-    toneType = getToneForRegion(toneType);
     mpNewToneDesc = &sToneDescriptors[toneType];
 
     mDurationMs = durationMs;
diff --git a/media/libmediaplayerservice/Crypto.cpp b/media/libmediaplayerservice/Crypto.cpp
index 0e8f913..ae4d845 100644
--- a/media/libmediaplayerservice/Crypto.cpp
+++ b/media/libmediaplayerservice/Crypto.cpp
@@ -17,6 +17,8 @@
 //#define LOG_NDEBUG 0
 #define LOG_TAG "Crypto"
 #include <utils/Log.h>
+#include <dirent.h>
+#include <dlfcn.h>
 
 #include "Crypto.h"
 
@@ -26,87 +28,176 @@
 #include <media/stagefright/foundation/hexdump.h>
 #include <media/stagefright/MediaErrors.h>
 
-#include <dlfcn.h>
-
 namespace android {
 
+KeyedVector<Vector<uint8_t>, String8> Crypto::mUUIDToLibraryPathMap;
+KeyedVector<String8, wp<SharedLibrary> > Crypto::mLibraryPathToOpenLibraryMap;
+Mutex Crypto::mMapLock;
+
+static bool operator<(const Vector<uint8_t> &lhs, const Vector<uint8_t> &rhs) {
+    if (lhs.size() < rhs.size()) {
+        return true;
+    } else if (lhs.size() > rhs.size()) {
+        return false;
+    }
+
+    return memcmp((void *)lhs.array(), (void *)rhs.array(), rhs.size()) < 0;
+}
+
 Crypto::Crypto()
     : mInitCheck(NO_INIT),
-      mLibHandle(NULL),
       mFactory(NULL),
       mPlugin(NULL) {
-    mInitCheck = init();
 }
 
 Crypto::~Crypto() {
     delete mPlugin;
     mPlugin = NULL;
+    closeFactory();
+}
 
+void Crypto::closeFactory() {
     delete mFactory;
     mFactory = NULL;
-
-    if (mLibHandle != NULL) {
-        dlclose(mLibHandle);
-        mLibHandle = NULL;
-    }
+    mLibrary.clear();
 }
 
 status_t Crypto::initCheck() const {
     return mInitCheck;
 }
 
-status_t Crypto::init() {
-    mLibHandle = dlopen("libdrmdecrypt.so", RTLD_NOW);
+/*
+ * Search the plugins directory for a plugin that supports the scheme
+ * specified by uuid
+ *
+ * If found:
+ *    mLibrary holds a strong pointer to the dlopen'd library
+ *    mFactory is set to the library's factory method
+ *    mInitCheck is set to OK
+ *
+ * If not found:
+ *    mLibrary is cleared and mFactory are set to NULL
+ *    mInitCheck is set to an error (!OK)
+ */
+void Crypto::findFactoryForScheme(const uint8_t uuid[16]) {
 
-    if (mLibHandle == NULL) {
-        ALOGE("Unable to locate libdrmdecrypt.so");
+    closeFactory();
 
-        return ERROR_UNSUPPORTED;
+    // lock static maps
+    Mutex::Autolock autoLock(mMapLock);
+
+    // first check cache
+    Vector<uint8_t> uuidVector;
+    uuidVector.appendArray(uuid, sizeof(uuid));
+    ssize_t index = mUUIDToLibraryPathMap.indexOfKey(uuidVector);
+    if (index >= 0) {
+        if (loadLibraryForScheme(mUUIDToLibraryPathMap[index], uuid)) {
+            mInitCheck = OK;
+            return;
+        } else {
+            ALOGE("Failed to load from cached library path!");
+            mInitCheck = ERROR_UNSUPPORTED;
+            return;
+        }
+    }
+
+    // no luck, have to search
+    String8 dirPath("/vendor/lib/mediadrm");
+    String8 pluginPath;
+
+    DIR* pDir = opendir(dirPath.string());
+    if (pDir) {
+        struct dirent* pEntry;
+        while ((pEntry = readdir(pDir))) {
+
+            pluginPath = dirPath + "/" + pEntry->d_name;
+
+            if (pluginPath.getPathExtension() == ".so") {
+
+                if (loadLibraryForScheme(pluginPath, uuid)) {
+                    mUUIDToLibraryPathMap.add(uuidVector, pluginPath);
+                    mInitCheck = OK;
+                    closedir(pDir);
+                    return;
+                }
+            }
+        }
+
+        closedir(pDir);
+    }
+
+    // try the legacy libdrmdecrypt.so
+    pluginPath = "libdrmdecrypt.so";
+    if (loadLibraryForScheme(pluginPath, uuid)) {
+        mUUIDToLibraryPathMap.add(uuidVector, pluginPath);
+        mInitCheck = OK;
+        return;
+    }
+
+    ALOGE("Failed to find crypto plugin");
+    mInitCheck = ERROR_UNSUPPORTED;
+}
+
+bool Crypto::loadLibraryForScheme(const String8 &path, const uint8_t uuid[16]) {
+
+    // get strong pointer to open shared library
+    ssize_t index = mLibraryPathToOpenLibraryMap.indexOfKey(path);
+    if (index >= 0) {
+        mLibrary = mLibraryPathToOpenLibraryMap[index].promote();
+    } else {
+        index = mLibraryPathToOpenLibraryMap.add(path, NULL);
+    }
+
+    if (!mLibrary.get()) {
+        mLibrary = new SharedLibrary(path);
+        if (!*mLibrary) {
+            return false;
+        }
+
+        mLibraryPathToOpenLibraryMap.replaceValueAt(index, mLibrary);
     }
 
     typedef CryptoFactory *(*CreateCryptoFactoryFunc)();
+
     CreateCryptoFactoryFunc createCryptoFactory =
-        (CreateCryptoFactoryFunc)dlsym(mLibHandle, "createCryptoFactory");
+        (CreateCryptoFactoryFunc)mLibrary->lookup("createCryptoFactory");
 
-    if (createCryptoFactory == NULL
-            || ((mFactory = createCryptoFactory()) == NULL)) {
-        if (createCryptoFactory == NULL) {
-            ALOGE("Unable to find symbol 'createCryptoFactory'.");
-        } else {
-            ALOGE("createCryptoFactory() failed.");
-        }
-
-        dlclose(mLibHandle);
-        mLibHandle = NULL;
-
-        return ERROR_UNSUPPORTED;
-    }
-
-    return OK;
-}
-
-bool Crypto::isCryptoSchemeSupported(const uint8_t uuid[16]) const {
-    Mutex::Autolock autoLock(mLock);
-
-    if (mInitCheck != OK) {
+    if (createCryptoFactory == NULL ||
+        (mFactory = createCryptoFactory()) == NULL ||
+        !mFactory->isCryptoSchemeSupported(uuid)) {
+        closeFactory();
         return false;
     }
+    return true;
+}
 
-    return mFactory->isCryptoSchemeSupported(uuid);
+bool Crypto::isCryptoSchemeSupported(const uint8_t uuid[16]) {
+    Mutex::Autolock autoLock(mLock);
+
+    if (mFactory && mFactory->isCryptoSchemeSupported(uuid)) {
+        return true;
+    }
+
+    findFactoryForScheme(uuid);
+    return (mInitCheck == OK);
 }
 
 status_t Crypto::createPlugin(
         const uint8_t uuid[16], const void *data, size_t size) {
     Mutex::Autolock autoLock(mLock);
 
-    if (mInitCheck != OK) {
-        return mInitCheck;
-    }
-
     if (mPlugin != NULL) {
         return -EINVAL;
     }
 
+    if (!mFactory || !mFactory->isCryptoSchemeSupported(uuid)) {
+        findFactoryForScheme(uuid);
+    }
+
+    if (mInitCheck != OK) {
+        return mInitCheck;
+    }
+
     return mFactory->createPlugin(uuid, data, size, &mPlugin);
 }
 
diff --git a/media/libmediaplayerservice/Crypto.h b/media/libmediaplayerservice/Crypto.h
index d066774..c44ae34 100644
--- a/media/libmediaplayerservice/Crypto.h
+++ b/media/libmediaplayerservice/Crypto.h
@@ -20,6 +20,9 @@
 
 #include <media/ICrypto.h>
 #include <utils/threads.h>
+#include <utils/KeyedVector.h>
+
+#include "SharedLibrary.h"
 
 namespace android {
 
@@ -32,7 +35,7 @@
 
     virtual status_t initCheck() const;
 
-    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]) const;
+    virtual bool isCryptoSchemeSupported(const uint8_t uuid[16]);
 
     virtual status_t createPlugin(
             const uint8_t uuid[16], const void *data, size_t size);
@@ -56,11 +59,17 @@
     mutable Mutex mLock;
 
     status_t mInitCheck;
-    void *mLibHandle;
+    sp<SharedLibrary> mLibrary;
     CryptoFactory *mFactory;
     CryptoPlugin *mPlugin;
 
-    status_t init();
+    static KeyedVector<Vector<uint8_t>, String8> mUUIDToLibraryPathMap;
+    static KeyedVector<String8, wp<SharedLibrary> > mLibraryPathToOpenLibraryMap;
+    static Mutex mMapLock;
+
+    void findFactoryForScheme(const uint8_t uuid[16]);
+    bool loadLibraryForScheme(const String8 &path, const uint8_t uuid[16]);
+    void closeFactory();
 
     DISALLOW_EVIL_CONSTRUCTORS(Crypto);
 };
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
index 8ed07bf..b0df379 100644
--- a/media/libstagefright/Utils.cpp
+++ b/media/libstagefright/Utils.cpp
@@ -21,7 +21,7 @@
 #include "include/ESDS.h"
 
 #include <arpa/inet.h>
-
+#include <cutils/properties.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
@@ -455,6 +455,21 @@
 #endif
 }
 
+AString MakeUserAgent() {
+    AString ua;
+    ua.append("stagefright/1.2 (Linux;Android ");
+
+#if (PROPERTY_VALUE_MAX < 8)
+#error "PROPERTY_VALUE_MAX must be at least 8"
+#endif
+
+    char value[PROPERTY_VALUE_MAX];
+    property_get("ro.build.version.release", value, "Unknown");
+    ua.append(value);
+    ua.append(")");
+
+    return ua;
+}
 
 }  // namespace android
 
diff --git a/media/libstagefright/chromium_http/support.cpp b/media/libstagefright/chromium_http/support.cpp
index 13ae3df..832e86d 100644
--- a/media/libstagefright/chromium_http/support.cpp
+++ b/media/libstagefright/chromium_http/support.cpp
@@ -36,8 +36,8 @@
 #include "include/ChromiumHTTPDataSource.h"
 
 #include <cutils/log.h>
-#include <cutils/properties.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
 #include <string>
 
 namespace android {
@@ -156,19 +156,7 @@
 ////////////////////////////////////////////////////////////////////////////////
 
 SfRequestContext::SfRequestContext() {
-    AString ua;
-    ua.append("stagefright/1.2 (Linux;Android ");
-
-#if (PROPERTY_VALUE_MAX < 8)
-#error "PROPERTY_VALUE_MAX must be at least 8"
-#endif
-
-    char value[PROPERTY_VALUE_MAX];
-    property_get("ro.build.version.release", value, "Unknown");
-    ua.append(value);
-    ua.append(")");
-
-    mUserAgent = ua.c_str();
+    mUserAgent = MakeUserAgent().c_str();
 
     set_net_log(new SfNetLog());
 
diff --git a/media/libstagefright/rtsp/ARTSPConnection.cpp b/media/libstagefright/rtsp/ARTSPConnection.cpp
index 161bd4f..3068541 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.cpp
+++ b/media/libstagefright/rtsp/ARTSPConnection.cpp
@@ -20,13 +20,12 @@
 
 #include "ARTSPConnection.h"
 
-#include <cutils/properties.h>
-
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/foundation/base64.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
 
 #include <arpa/inet.h>
 #include <fcntl.h>
@@ -41,6 +40,10 @@
 // static
 const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll;
 
+// static
+const AString ARTSPConnection::sUserAgent =
+    StringPrintf("User-Agent: %s\r\n", MakeUserAgent().c_str());
+
 ARTSPConnection::ARTSPConnection(bool uidValid, uid_t uid)
     : mUIDValid(uidValid),
       mUID(uid),
@@ -50,7 +53,6 @@
       mConnectionID(0),
       mNextCSeq(0),
       mReceiveResponseEventPending(false) {
-    MakeUserAgent(&mUserAgent);
 }
 
 ARTSPConnection::~ARTSPConnection() {
@@ -1032,27 +1034,12 @@
 #endif
 }
 
-// static
-void ARTSPConnection::MakeUserAgent(AString *userAgent) {
-    userAgent->clear();
-    userAgent->setTo("User-Agent: stagefright/1.1 (Linux;Android ");
-
-#if (PROPERTY_VALUE_MAX < 8)
-#error "PROPERTY_VALUE_MAX must be at least 8"
-#endif
-
-    char value[PROPERTY_VALUE_MAX];
-    property_get("ro.build.version.release", value, "Unknown");
-    userAgent->append(value);
-    userAgent->append(")\r\n");
-}
-
 void ARTSPConnection::addUserAgent(AString *request) const {
     // Find the boundary between headers and the body.
     ssize_t i = request->find("\r\n\r\n");
     CHECK_GE(i, 0);
 
-    request->insert(mUserAgent, i + 2);
+    request->insert(sUserAgent, i + 2);
 }
 
 }  // namespace android
diff --git a/media/libstagefright/rtsp/ARTSPConnection.h b/media/libstagefright/rtsp/ARTSPConnection.h
index 68f2d59..1fe9c99 100644
--- a/media/libstagefright/rtsp/ARTSPConnection.h
+++ b/media/libstagefright/rtsp/ARTSPConnection.h
@@ -74,6 +74,8 @@
 
     static const int64_t kSelectTimeoutUs;
 
+    static const AString sUserAgent;
+
     bool mUIDValid;
     uid_t mUID;
     State mState;
@@ -89,8 +91,6 @@
 
     sp<AMessage> mObserveBinaryMessage;
 
-    AString mUserAgent;
-
     void performDisconnect();
 
     void onConnect(const sp<AMessage> &msg);
@@ -122,8 +122,6 @@
     static bool ParseSingleUnsignedLong(
             const char *from, unsigned long *x);
 
-    static void MakeUserAgent(AString *userAgent);
-
     DISALLOW_EVIL_CONSTRUCTORS(ARTSPConnection);
 };
 
diff --git a/media/libstagefright/rtsp/MyHandler.h b/media/libstagefright/rtsp/MyHandler.h
index 95ed43a..e067e20 100644
--- a/media/libstagefright/rtsp/MyHandler.h
+++ b/media/libstagefright/rtsp/MyHandler.h
@@ -28,13 +28,13 @@
 #include "ASessionDescription.h"
 
 #include <ctype.h>
-#include <cutils/properties.h>
 
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/ALooper.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
 
 #include <arpa/inet.h>
 #include <sys/socket.h>
@@ -56,19 +56,6 @@
 
 namespace android {
 
-static void MakeUserAgentString(AString *s) {
-    s->setTo("stagefright/1.1 (Linux;Android ");
-
-#if (PROPERTY_VALUE_MAX < 8)
-#error "PROPERTY_VALUE_MAX must be at least 8"
-#endif
-
-    char value[PROPERTY_VALUE_MAX];
-    property_get("ro.build.version.release", value, "Unknown");
-    s->append(value);
-    s->append(")");
-}
-
 static bool GetAttribute(const char *s, const char *key, AString *value) {
     value->clear();
 
@@ -279,8 +266,7 @@
 
         data[offset++] = 6;  // TOOL
 
-        AString tool;
-        MakeUserAgentString(&tool);
+        AString tool = MakeUserAgent();
 
         data[offset++] = tool.size();
 
diff --git a/media/libstagefright/wifi-display/ANetworkSession.cpp b/media/libstagefright/wifi-display/ANetworkSession.cpp
index df20ae2..88ca1cc 100644
--- a/media/libstagefright/wifi-display/ANetworkSession.cpp
+++ b/media/libstagefright/wifi-display/ANetworkSession.cpp
@@ -565,7 +565,7 @@
         mSawSendFailure = true;
     }
 
-#if 1
+#if 0
     int numBytesQueued;
     int res = ioctl(mSocket, SIOCOUTQ, &numBytesQueued);
     if (res == 0 && numBytesQueued > 50 * 1024) {
@@ -576,7 +576,7 @@
         int64_t nowUs = ALooper::GetNowUs();
 
         if (mLastStallReportUs < 0ll
-                || nowUs > mLastStallReportUs + 500000ll) {
+                || nowUs > mLastStallReportUs + 100000ll) {
             sp<AMessage> msg = mNotify->dup();
             msg->setInt32("sessionID", mSessionID);
             msg->setInt32("reason", kWhatNetworkStall);
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index f81929c..f1f9f45 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -12,7 +12,6 @@
         rtp/RTPReceiver.cpp             \
         rtp/RTPSender.cpp               \
         sink/DirectRenderer.cpp         \
-        sink/TunnelRenderer.cpp         \
         sink/WifiDisplaySink.cpp        \
         SNTPClient.cpp                  \
         TimeSyncer.cpp                  \
diff --git a/media/libstagefright/wifi-display/MediaSender.cpp b/media/libstagefright/wifi-display/MediaSender.cpp
index d13a92e..33af66d 100644
--- a/media/libstagefright/wifi-display/MediaSender.cpp
+++ b/media/libstagefright/wifi-display/MediaSender.cpp
@@ -85,10 +85,11 @@
 
 status_t MediaSender::initAsync(
         ssize_t trackIndex,
-        RTPSender::TransportMode transportMode,
         const char *remoteHost,
         int32_t remoteRTPPort,
+        RTPSender::TransportMode rtpMode,
         int32_t remoteRTCPPort,
+        RTPSender::TransportMode rtcpMode,
         int32_t *localRTPPort) {
     if (trackIndex < 0) {
         if (mMode != MODE_UNDEFINED) {
@@ -124,10 +125,11 @@
             looper()->registerHandler(mTSSender);
 
             err = mTSSender->initAsync(
-                    transportMode,
                     remoteHost,
                     remoteRTPPort,
+                    rtpMode,
                     remoteRTCPPort,
+                    rtcpMode,
                     localRTPPort);
 
             if (err != OK) {
@@ -174,10 +176,11 @@
     looper()->registerHandler(info->mSender);
 
     status_t err = info->mSender->initAsync(
-            transportMode,
             remoteHost,
             remoteRTPPort,
+            rtpMode,
             remoteRTCPPort,
+            rtcpMode,
             localRTPPort);
 
     if (err != OK) {
@@ -260,37 +263,6 @@
                         tsPackets,
                         33 /* packetType */,
                         RTPSender::PACKETIZATION_TRANSPORT_STREAM);
-
-#if 0
-                {
-                    int64_t nowUs = ALooper::GetNowUs();
-
-                    int64_t timeUs;
-                    CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
-
-                    int64_t delayMs = (nowUs - timeUs) / 1000ll;
-
-                    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("[%lld]: (%4lld ms) %s\n",
-                          timeUs / 1000,
-                          delayMs,
-                          kPattern + kPatternSize - n);
-                }
-#endif
             }
 
             if (err != OK) {
@@ -369,6 +341,22 @@
             break;
         }
 
+        case kWhatInformSender:
+        {
+            int64_t avgLatencyUs;
+            CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+            int64_t maxLatencyUs;
+            CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+            sp<AMessage> notify = mNotify->dup();
+            notify->setInt32("what", kWhatInformSender);
+            notify->setInt64("avgLatencyUs", avgLatencyUs);
+            notify->setInt64("maxLatencyUs", maxLatencyUs);
+            notify->post();
+            break;
+        }
+
         default:
             TRESPASS();
     }
diff --git a/media/libstagefright/wifi-display/MediaSender.h b/media/libstagefright/wifi-display/MediaSender.h
index 447abf7..04538ea 100644
--- a/media/libstagefright/wifi-display/MediaSender.h
+++ b/media/libstagefright/wifi-display/MediaSender.h
@@ -43,6 +43,7 @@
         kWhatInitDone,
         kWhatError,
         kWhatNetworkStall,
+        kWhatInformSender,
     };
 
     MediaSender(
@@ -59,10 +60,11 @@
     // If trackIndex == -1, initialize for transport stream muxing.
     status_t initAsync(
             ssize_t trackIndex,
-            RTPSender::TransportMode transportMode,
             const char *remoteHost,
             int32_t remoteRTPPort,
+            RTPSender::TransportMode rtpMode,
             int32_t remoteRTCPPort,
+            RTPSender::TransportMode rtcpMode,
             int32_t *localRTPPort);
 
     status_t queueAccessUnit(
diff --git a/media/libstagefright/wifi-display/rtp/RTPBase.h b/media/libstagefright/wifi-display/rtp/RTPBase.h
index 6507a6f..e3fa845 100644
--- a/media/libstagefright/wifi-display/rtp/RTPBase.h
+++ b/media/libstagefright/wifi-display/rtp/RTPBase.h
@@ -29,6 +29,7 @@
 
     enum TransportMode {
         TRANSPORT_UNDEFINED,
+        TRANSPORT_NONE,
         TRANSPORT_UDP,
         TRANSPORT_TCP,
         TRANSPORT_TCP_INTERLEAVED,
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.cpp b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
index c8e265c..9eeeabd 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.cpp
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.cpp
@@ -38,7 +38,8 @@
         const sp<AMessage> &notify)
     : mNetSession(netSession),
       mNotify(notify),
-      mMode(TRANSPORT_UNDEFINED),
+      mRTPMode(TRANSPORT_UNDEFINED),
+      mRTCPMode(TRANSPORT_UNDEFINED),
       mRTPSessionID(0),
       mRTCPSessionID(0),
       mRTPConnected(false),
@@ -74,18 +75,24 @@
 }
 
 status_t RTPSender::initAsync(
-        TransportMode mode,
         const char *remoteHost,
         int32_t remoteRTPPort,
+        TransportMode rtpMode,
         int32_t remoteRTCPPort,
+        TransportMode rtcpMode,
         int32_t *outLocalRTPPort) {
-    if (mMode != TRANSPORT_UNDEFINED || mode == TRANSPORT_UNDEFINED) {
+    if (mRTPMode != TRANSPORT_UNDEFINED
+            || rtpMode == TRANSPORT_UNDEFINED
+            || rtpMode == TRANSPORT_NONE
+            || rtcpMode == TRANSPORT_UNDEFINED) {
         return INVALID_OPERATION;
     }
 
-    CHECK_NE(mMode, TRANSPORT_TCP_INTERLEAVED);
+    CHECK_NE(rtpMode, TRANSPORT_TCP_INTERLEAVED);
+    CHECK_NE(rtcpMode, TRANSPORT_TCP_INTERLEAVED);
 
-    if (mode == TRANSPORT_TCP && remoteRTCPPort >= 0) {
+    if ((rtcpMode == TRANSPORT_NONE && remoteRTCPPort >= 0)
+            || (rtcpMode != TRANSPORT_NONE && remoteRTCPPort < 0)) {
         return INVALID_OPERATION;
     }
 
@@ -105,7 +112,7 @@
         localRTPPort = PickRandomRTPPort();
 
         status_t err;
-        if (mode == TRANSPORT_UDP) {
+        if (rtpMode == TRANSPORT_UDP) {
             err = mNetSession->createUDPSession(
                     localRTPPort,
                     remoteHost,
@@ -113,7 +120,7 @@
                     rtpNotify,
                     &mRTPSessionID);
         } else {
-            CHECK_EQ(mode, TRANSPORT_TCP);
+            CHECK_EQ(rtpMode, TRANSPORT_TCP);
             err = mNetSession->createTCPDatagramSession(
                     localRTPPort,
                     remoteHost,
@@ -130,7 +137,7 @@
             break;
         }
 
-        if (mode == TRANSPORT_UDP) {
+        if (rtcpMode == TRANSPORT_UDP) {
             err = mNetSession->createUDPSession(
                     localRTPPort + 1,
                     remoteHost,
@@ -138,7 +145,7 @@
                     rtcpNotify,
                     &mRTCPSessionID);
         } else {
-            CHECK_EQ(mode, TRANSPORT_TCP);
+            CHECK_EQ(rtcpMode, TRANSPORT_TCP);
             err = mNetSession->createTCPDatagramSession(
                     localRTPPort + 1,
                     remoteHost,
@@ -155,15 +162,20 @@
         mRTPSessionID = 0;
     }
 
-    if (mode == TRANSPORT_UDP) {
+    if (rtpMode == TRANSPORT_UDP) {
         mRTPConnected = true;
+    }
+
+    if (rtcpMode == TRANSPORT_UDP) {
         mRTCPConnected = true;
     }
 
-    mMode = mode;
+    mRTPMode = rtpMode;
+    mRTCPMode = rtcpMode;
     *outLocalRTPPort = localRTPPort;
 
-    if (mMode == TRANSPORT_UDP) {
+    if (mRTPMode == TRANSPORT_UDP
+            && (mRTCPMode == TRANSPORT_UDP || mRTCPMode == TRANSPORT_NONE)) {
         notifyInitDone(OK);
     }
 
@@ -496,12 +508,12 @@
                 mRTCPSessionID = 0;
             }
 
-            if (mMode == TRANSPORT_TCP) {
-                if (!mRTPConnected
-                        || (mRTCPSessionID > 0 && !mRTCPConnected)) {
-                    notifyInitDone(err);
-                    break;
-                }
+            if (!mRTPConnected
+                    || (mRTPMode != TRANSPORT_NONE && !mRTCPConnected)) {
+                // We haven't completed initialization, attach the error
+                // to the notification instead.
+                notifyInitDone(err);
+                break;
             }
 
             notifyError(err);
@@ -523,20 +535,21 @@
 
         case ANetworkSession::kWhatConnected:
         {
-            CHECK_EQ(mMode, TRANSPORT_TCP);
-
             int32_t sessionID;
             CHECK(msg->findInt32("sessionID", &sessionID));
 
             if  (isRTP) {
+                CHECK_EQ(mRTPMode, TRANSPORT_TCP);
                 CHECK_EQ(sessionID, mRTPSessionID);
                 mRTPConnected = true;
             } else {
+                CHECK_EQ(mRTCPMode, TRANSPORT_TCP);
                 CHECK_EQ(sessionID, mRTCPSessionID);
                 mRTCPConnected = true;
             }
 
-            if (mRTPConnected && (mRTCPSessionID == 0 || mRTCPConnected)) {
+            if (mRTPConnected
+                    && (mRTCPMode == TRANSPORT_NONE || mRTCPConnected)) {
                 notifyInitDone(OK);
             }
             break;
@@ -603,6 +616,7 @@
                 break;
 
             case 204:  // APP
+                parseAPP(data, headerLength);
                 break;
 
             case 205:  // TSFB (transport layer specific feedback)
@@ -708,6 +722,21 @@
     return OK;
 }
 
+status_t RTPSender::parseAPP(const uint8_t *data, size_t size) {
+    if (!memcmp("late", &data[8], 4)) {
+        int64_t avgLatencyUs = (int64_t)U64_AT(&data[12]);
+        int64_t maxLatencyUs = (int64_t)U64_AT(&data[20]);
+
+        sp<AMessage> notify = mNotify->dup();
+        notify->setInt32("what", kWhatInformSender);
+        notify->setInt64("avgLatencyUs", avgLatencyUs);
+        notify->setInt64("maxLatencyUs", maxLatencyUs);
+        notify->post();
+    }
+
+    return OK;
+}
+
 void RTPSender::notifyInitDone(status_t err) {
     sp<AMessage> notify = mNotify->dup();
     notify->setInt32("what", kWhatInitDone);
diff --git a/media/libstagefright/wifi-display/rtp/RTPSender.h b/media/libstagefright/wifi-display/rtp/RTPSender.h
index 90b1796..3a926ea 100644
--- a/media/libstagefright/wifi-display/rtp/RTPSender.h
+++ b/media/libstagefright/wifi-display/rtp/RTPSender.h
@@ -37,16 +37,18 @@
         kWhatInitDone,
         kWhatError,
         kWhatNetworkStall,
+        kWhatInformSender,
     };
     RTPSender(
             const sp<ANetworkSession> &netSession,
             const sp<AMessage> &notify);
 
     status_t initAsync(
-              TransportMode mode,
               const char *remoteHost,
               int32_t remoteRTPPort,
+              TransportMode rtpMode,
               int32_t remoteRTCPPort,
+              TransportMode rtcpMode,
               int32_t *outLocalRTPPort);
 
     status_t queueBuffer(
@@ -72,7 +74,8 @@
 
     sp<ANetworkSession> mNetSession;
     sp<AMessage> mNotify;
-    TransportMode mMode;
+    TransportMode mRTPMode;
+    TransportMode mRTCPMode;
     int32_t mRTPSessionID;
     int32_t mRTCPSessionID;
     bool mRTPConnected;
@@ -103,6 +106,7 @@
     status_t onRTCPData(const sp<ABuffer> &data);
     status_t parseReceiverReport(const uint8_t *data, size_t size);
     status_t parseTSFB(const uint8_t *data, size_t size);
+    status_t parseAPP(const uint8_t *data, size_t size);
 
     void notifyInitDone(status_t err);
     void notifyError(status_t err);
diff --git a/media/libstagefright/wifi-display/sink/TunnelRenderer.cpp b/media/libstagefright/wifi-display/sink/TunnelRenderer.cpp
deleted file mode 100644
index 6b185db..0000000
--- a/media/libstagefright/wifi-display/sink/TunnelRenderer.cpp
+++ /dev/null
@@ -1,290 +0,0 @@
-/*
- * Copyright 2012, 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 "TunnelRenderer"
-#include <utils/Log.h>
-
-#include "TunnelRenderer.h"
-
-#include "ATSParser.h"
-
-#include <binder/IMemory.h>
-#include <binder/IServiceManager.h>
-#include <gui/SurfaceComposerClient.h>
-#include <media/IMediaPlayerService.h>
-#include <media/IStreamSource.h>
-#include <media/mediaplayer.h>
-#include <media/stagefright/foundation/ABuffer.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/AMessage.h>
-#include <ui/DisplayInfo.h>
-
-namespace android {
-
-struct TunnelRenderer::PlayerClient : public BnMediaPlayerClient {
-    PlayerClient() {}
-
-    virtual void notify(int msg, int ext1, int ext2, const Parcel *obj) {
-        ALOGI("notify %d, %d, %d", msg, ext1, ext2);
-    }
-
-protected:
-    virtual ~PlayerClient() {}
-
-private:
-    DISALLOW_EVIL_CONSTRUCTORS(PlayerClient);
-};
-
-struct TunnelRenderer::StreamSource : public BnStreamSource {
-    StreamSource(TunnelRenderer *owner);
-
-    virtual void setListener(const sp<IStreamListener> &listener);
-    virtual void setBuffers(const Vector<sp<IMemory> > &buffers);
-
-    virtual void onBufferAvailable(size_t index);
-
-    virtual uint32_t flags() const;
-
-    void doSomeWork();
-
-    void setTimeOffset(int64_t offset);
-
-protected:
-    virtual ~StreamSource();
-
-private:
-    mutable Mutex mLock;
-
-    TunnelRenderer *mOwner;
-
-    sp<IStreamListener> mListener;
-
-    Vector<sp<IMemory> > mBuffers;
-    List<size_t> mIndicesAvailable;
-
-    size_t mNumDeqeued;
-
-    int64_t mTimeOffsetUs;
-    bool mTimeOffsetChanged;
-
-    DISALLOW_EVIL_CONSTRUCTORS(StreamSource);
-};
-
-////////////////////////////////////////////////////////////////////////////////
-
-TunnelRenderer::StreamSource::StreamSource(TunnelRenderer *owner)
-    : mOwner(owner),
-      mNumDeqeued(0),
-      mTimeOffsetUs(0ll),
-      mTimeOffsetChanged(false) {
-}
-
-TunnelRenderer::StreamSource::~StreamSource() {
-}
-
-void TunnelRenderer::StreamSource::setListener(
-        const sp<IStreamListener> &listener) {
-    mListener = listener;
-}
-
-void TunnelRenderer::StreamSource::setBuffers(
-        const Vector<sp<IMemory> > &buffers) {
-    mBuffers = buffers;
-}
-
-void TunnelRenderer::StreamSource::onBufferAvailable(size_t index) {
-    CHECK_LT(index, mBuffers.size());
-
-    {
-        Mutex::Autolock autoLock(mLock);
-        mIndicesAvailable.push_back(index);
-    }
-
-    doSomeWork();
-}
-
-uint32_t TunnelRenderer::StreamSource::flags() const {
-    return kFlagAlignedVideoData | kFlagIsRealTimeData;
-}
-
-void TunnelRenderer::StreamSource::doSomeWork() {
-    Mutex::Autolock autoLock(mLock);
-
-    while (!mIndicesAvailable.empty()) {
-        sp<ABuffer> srcBuffer = mOwner->dequeueBuffer();
-        if (srcBuffer == NULL) {
-            break;
-        }
-
-        ++mNumDeqeued;
-
-        if (mTimeOffsetChanged) {
-            sp<AMessage> extra = new AMessage;
-
-            extra->setInt32(
-                    IStreamListener::kKeyDiscontinuityMask,
-                    ATSParser::DISCONTINUITY_TIME_OFFSET);
-
-            extra->setInt64("offset", mTimeOffsetUs);
-
-            mListener->issueCommand(
-                    IStreamListener::DISCONTINUITY,
-                    false /* synchronous */,
-                    extra);
-
-            mTimeOffsetChanged = false;
-        }
-
-        ALOGV("dequeue TS packet of size %d", srcBuffer->size());
-
-        size_t index = *mIndicesAvailable.begin();
-        mIndicesAvailable.erase(mIndicesAvailable.begin());
-
-        sp<IMemory> mem = mBuffers.itemAt(index);
-        CHECK_LE(srcBuffer->size(), mem->size());
-        CHECK_EQ((srcBuffer->size() % 188), 0u);
-
-        memcpy(mem->pointer(), srcBuffer->data(), srcBuffer->size());
-        mListener->queueBuffer(index, srcBuffer->size());
-    }
-}
-
-void TunnelRenderer::StreamSource::setTimeOffset(int64_t offset) {
-    Mutex::Autolock autoLock(mLock);
-
-    if (offset != mTimeOffsetUs) {
-        mTimeOffsetUs = offset;
-        mTimeOffsetChanged = true;
-    }
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-TunnelRenderer::TunnelRenderer(
-        const sp<IGraphicBufferProducer> &bufferProducer)
-    : mSurfaceTex(bufferProducer),
-      mStartup(true) {
-    mStreamSource = new StreamSource(this);
-}
-
-TunnelRenderer::~TunnelRenderer() {
-    destroyPlayer();
-}
-
-void TunnelRenderer::setTimeOffset(int64_t offset) {
-    mStreamSource->setTimeOffset(offset);
-}
-
-void TunnelRenderer::onMessageReceived(const sp<AMessage> &msg) {
-    switch (msg->what()) {
-        default:
-            TRESPASS();
-    }
-}
-
-void TunnelRenderer::initPlayer() {
-    if (mSurfaceTex == NULL) {
-        mComposerClient = new SurfaceComposerClient;
-        CHECK_EQ(mComposerClient->initCheck(), (status_t)OK);
-
-        DisplayInfo info;
-        SurfaceComposerClient::getDisplayInfo(0, &info);
-        ssize_t displayWidth = info.w;
-        ssize_t displayHeight = info.h;
-
-        mSurfaceControl =
-            mComposerClient->createSurface(
-                    String8("A Surface"),
-                    displayWidth,
-                    displayHeight,
-                    PIXEL_FORMAT_RGB_565,
-                    0);
-
-        CHECK(mSurfaceControl != NULL);
-        CHECK(mSurfaceControl->isValid());
-
-        SurfaceComposerClient::openGlobalTransaction();
-        CHECK_EQ(mSurfaceControl->setLayer(INT_MAX), (status_t)OK);
-        CHECK_EQ(mSurfaceControl->show(), (status_t)OK);
-        SurfaceComposerClient::closeGlobalTransaction();
-
-        mSurface = mSurfaceControl->getSurface();
-        CHECK(mSurface != NULL);
-    }
-
-    sp<IServiceManager> sm = defaultServiceManager();
-    sp<IBinder> binder = sm->getService(String16("media.player"));
-    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
-    CHECK(service.get() != NULL);
-
-    mPlayerClient = new PlayerClient;
-
-    mPlayer = service->create(mPlayerClient, 0);
-    CHECK(mPlayer != NULL);
-    CHECK_EQ(mPlayer->setDataSource(mStreamSource), (status_t)OK);
-
-    mPlayer->setVideoSurfaceTexture(
-            mSurfaceTex != NULL ? mSurfaceTex : mSurface->getIGraphicBufferProducer());
-
-    mPlayer->start();
-}
-
-void TunnelRenderer::destroyPlayer() {
-    mStreamSource.clear();
-
-    mPlayer->setVideoSurfaceTexture(NULL);
-
-    mPlayer->stop();
-    mPlayer.clear();
-
-    if (mSurfaceTex == NULL) {
-        mSurface.clear();
-        mSurfaceControl.clear();
-
-        mComposerClient->dispose();
-        mComposerClient.clear();
-    }
-}
-
-void TunnelRenderer::queueBuffer(const sp<ABuffer> &buffer) {
-    {
-        Mutex::Autolock autoLock(mLock);
-        mBuffers.push_back(buffer);
-    }
-
-    if (mStartup) {
-        initPlayer();
-        mStartup = false;
-    }
-
-    mStreamSource->doSomeWork();
-}
-
-sp<ABuffer> TunnelRenderer::dequeueBuffer() {
-    Mutex::Autolock autoLock(mLock);
-    if (mBuffers.empty()) {
-        return NULL;
-    }
-
-    sp<ABuffer> buf = *mBuffers.begin();
-    mBuffers.erase(mBuffers.begin());
-
-    return buf;
-}
-
-}  // namespace android
-
diff --git a/media/libstagefright/wifi-display/sink/TunnelRenderer.h b/media/libstagefright/wifi-display/sink/TunnelRenderer.h
deleted file mode 100644
index 479e73c..0000000
--- a/media/libstagefright/wifi-display/sink/TunnelRenderer.h
+++ /dev/null
@@ -1,78 +0,0 @@
-/*
- * Copyright 2012, 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 TUNNEL_RENDERER_H_
-
-#define TUNNEL_RENDERER_H_
-
-#include <gui/Surface.h>
-#include <media/stagefright/foundation/AHandler.h>
-
-namespace android {
-
-struct ABuffer;
-struct SurfaceComposerClient;
-struct SurfaceControl;
-struct Surface;
-struct IMediaPlayer;
-struct IStreamListener;
-
-// This class reassembles incoming RTP packets into the correct order
-// and sends the resulting transport stream to a mediaplayer instance
-// for playback.
-struct TunnelRenderer : public AHandler {
-    TunnelRenderer(const sp<IGraphicBufferProducer> &bufferProducer);
-
-    void queueBuffer(const sp<ABuffer> &buffer);
-    sp<ABuffer> dequeueBuffer();
-
-    void setTimeOffset(int64_t offset);
-
-    int64_t getAvgLatenessUs() {
-        return 0ll;
-    }
-
-protected:
-    virtual void onMessageReceived(const sp<AMessage> &msg);
-    virtual ~TunnelRenderer();
-
-private:
-    struct PlayerClient;
-    struct StreamSource;
-
-    mutable Mutex mLock;
-
-    sp<IGraphicBufferProducer> mSurfaceTex;
-
-    bool mStartup;
-    List<sp<ABuffer> > mBuffers;
-
-    sp<SurfaceComposerClient> mComposerClient;
-    sp<SurfaceControl> mSurfaceControl;
-    sp<Surface> mSurface;
-    sp<PlayerClient> mPlayerClient;
-    sp<IMediaPlayer> mPlayer;
-    sp<StreamSource> mStreamSource;
-
-    void initPlayer();
-    void destroyPlayer();
-
-    DISALLOW_EVIL_CONSTRUCTORS(TunnelRenderer);
-};
-
-}  // namespace android
-
-#endif  // TUNNEL_RENDERER_H_
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
index bb8c387..0a8462c 100644
--- a/media/libstagefright/wifi-display/source/Converter.cpp
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -135,7 +135,9 @@
     return mNeedToManuallyPrependSPSPPS;
 }
 
-static int32_t getBitrate(const char *propName, int32_t defaultValue) {
+// static
+int32_t Converter::GetInt32Property(
+        const char *propName, int32_t defaultValue) {
     char val[PROPERTY_VALUE_MAX];
     if (property_get(propName, val, NULL)) {
         char *end;
@@ -185,8 +187,8 @@
 
     mOutputFormat->setString("mime", outputMIME.c_str());
 
-    int32_t audioBitrate = getBitrate("media.wfd.audio-bitrate", 128000);
-    int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 5000000);
+    int32_t audioBitrate = GetInt32Property("media.wfd.audio-bitrate", 128000);
+    int32_t videoBitrate = GetInt32Property("media.wfd.video-bitrate", 5000000);
     mPrevVideoBitrate = videoBitrate;
 
     ALOGI("using audio bitrate of %d bps, video bitrate of %d bps",
@@ -622,18 +624,6 @@
 }
 
 status_t Converter::doMoreWork() {
-    if (mIsVideo) {
-        int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 5000000);
-        if (videoBitrate != mPrevVideoBitrate) {
-            sp<AMessage> params = new AMessage;
-
-            params->setInt32("videoBitrate", videoBitrate);
-            mEncoder->setParameters(params);
-
-            mPrevVideoBitrate = videoBitrate;
-        }
-    }
-
     status_t err;
 
     for (;;) {
@@ -708,4 +698,19 @@
     (new AMessage(kWhatDropAFrame, id()))->post();
 }
 
+int32_t Converter::getVideoBitrate() const {
+    return mPrevVideoBitrate;
+}
+
+void Converter::setVideoBitrate(int32_t bitRate) {
+    if (mIsVideo && mEncoder != NULL && bitRate != mPrevVideoBitrate) {
+        sp<AMessage> params = new AMessage;
+        params->setInt32("videoBitrate", bitRate);
+
+        mEncoder->setParameters(params);
+
+        mPrevVideoBitrate = bitRate;
+    }
+}
+
 }  // namespace android
diff --git a/media/libstagefright/wifi-display/source/Converter.h b/media/libstagefright/wifi-display/source/Converter.h
index a418f69..ba297c4 100644
--- a/media/libstagefright/wifi-display/source/Converter.h
+++ b/media/libstagefright/wifi-display/source/Converter.h
@@ -70,6 +70,11 @@
 
     void shutdownAsync();
 
+    int32_t getVideoBitrate() const;
+    void setVideoBitrate(int32_t bitrate);
+
+    static int32_t GetInt32Property(const char *propName, int32_t defaultValue);
+
 protected:
     virtual ~Converter();
     virtual void onMessageReceived(const sp<AMessage> &msg);
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index a3b6542..715d0b5 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -27,6 +27,7 @@
 #include "WifiDisplaySource.h"
 
 #include <binder/IServiceManager.h>
+#include <cutils/properties.h>
 #include <media/IHDCP.h>
 #include <media/stagefright/foundation/ABitReader.h>
 #include <media/stagefright/foundation/ABuffer.h>
@@ -66,6 +67,7 @@
     bool isAudio() const;
 
     const sp<Converter> &converter() const;
+    const sp<RepeaterSource> &repeaterSource() const;
 
     ssize_t mediaSenderTrackIndex() const;
     void setMediaSenderTrackIndex(size_t index);
@@ -171,6 +173,11 @@
     return mConverter;
 }
 
+const sp<RepeaterSource> &
+WifiDisplaySource::PlaybackSession::Track::repeaterSource() const {
+    return mRepeaterSource;
+}
+
 ssize_t WifiDisplaySource::PlaybackSession::Track::mediaSenderTrackIndex() const {
     CHECK_GE(mMediaSenderTrackIndex, 0);
     return mMediaSenderTrackIndex;
@@ -362,8 +369,11 @@
 }
 
 status_t WifiDisplaySource::PlaybackSession::init(
-        const char *clientIP, int32_t clientRtp, int32_t clientRtcp,
-        RTPSender::TransportMode transportMode,
+        const char *clientIP,
+        int32_t clientRtp,
+        RTPSender::TransportMode rtpMode,
+        int32_t clientRtcp,
+        RTPSender::TransportMode rtcpMode,
         bool enableAudio,
         bool usePCMAudio,
         bool enableVideo,
@@ -385,10 +395,11 @@
     if (err == OK) {
         err = mMediaSender->initAsync(
                 -1 /* trackIndex */,
-                transportMode,
                 clientIP,
                 clientRtp,
+                rtpMode,
                 clientRtcp,
+                rtcpMode,
                 &mLocalRTPPort);
     }
 
@@ -548,6 +559,8 @@
                         converter->dropAFrame();
                     }
                 }
+            } else if (what == MediaSender::kWhatInformSender) {
+                onSinkFeedback(msg);
             } else {
                 TRESPASS();
             }
@@ -643,6 +656,86 @@
     }
 }
 
+void WifiDisplaySource::PlaybackSession::onSinkFeedback(const sp<AMessage> &msg) {
+    int64_t avgLatencyUs;
+    CHECK(msg->findInt64("avgLatencyUs", &avgLatencyUs));
+
+    int64_t maxLatencyUs;
+    CHECK(msg->findInt64("maxLatencyUs", &maxLatencyUs));
+
+    ALOGI("sink reports avg. latency of %lld ms (max %lld ms)",
+          avgLatencyUs / 1000ll,
+          maxLatencyUs / 1000ll);
+
+    if (mVideoTrackIndex >= 0) {
+        const sp<Track> &videoTrack = mTracks.valueFor(mVideoTrackIndex);
+        sp<Converter> converter = videoTrack->converter();
+
+        if (converter != NULL) {
+            int32_t videoBitrate =
+                Converter::GetInt32Property("media.wfd.video-bitrate", -1);
+
+            char val[PROPERTY_VALUE_MAX];
+            if (videoBitrate < 0
+                    && property_get("media.wfd.video-bitrate", val, NULL)
+                    && !strcasecmp("adaptive", val)) {
+                videoBitrate = converter->getVideoBitrate();
+
+                if (avgLatencyUs > 300000ll) {
+                    videoBitrate *= 0.6;
+                } else if (avgLatencyUs < 100000ll) {
+                    videoBitrate *= 1.1;
+                }
+            }
+
+            if (videoBitrate > 0) {
+                if (videoBitrate < 500000) {
+                    videoBitrate = 500000;
+                } else if (videoBitrate > 10000000) {
+                    videoBitrate = 10000000;
+                }
+
+                if (videoBitrate != converter->getVideoBitrate()) {
+                    ALOGI("setting video bitrate to %d bps", videoBitrate);
+
+                    converter->setVideoBitrate(videoBitrate);
+                }
+            }
+        }
+
+        sp<RepeaterSource> repeaterSource = videoTrack->repeaterSource();
+        if (repeaterSource != NULL) {
+            double rateHz =
+                Converter::GetInt32Property(
+                        "media.wfd.video-framerate", -1);
+
+            if (rateHz < 0.0) {
+                rateHz = repeaterSource->getFrameRate();
+
+                if (avgLatencyUs > 300000ll) {
+                    rateHz *= 0.9;
+                } else if (avgLatencyUs < 200000ll) {
+                    rateHz *= 1.1;
+                }
+            }
+
+            if (rateHz > 0) {
+                if (rateHz < 5.0) {
+                    rateHz = 5.0;
+                } else if (rateHz > 30.0) {
+                    rateHz = 30.0;
+                }
+
+                if (rateHz != repeaterSource->getFrameRate()) {
+                    ALOGI("setting frame rate to %.2f Hz", rateHz);
+
+                    repeaterSource->setFrameRate(rateHz);
+                }
+            }
+        }
+    }
+}
+
 status_t WifiDisplaySource::PlaybackSession::setupMediaPacketizer(
         bool enableAudio, bool enableVideo) {
     DataSource::RegisterDefaultSniffers();
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
index da207e2..39086a1 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.h
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -44,8 +44,11 @@
             const char *path = NULL);
 
     status_t init(
-            const char *clientIP, int32_t clientRtp, int32_t clientRtcp,
-            RTPSender::TransportMode transportMode,
+            const char *clientIP,
+            int32_t clientRtp,
+            RTPSender::TransportMode rtpMode,
+            int32_t clientRtcp,
+            RTPSender::TransportMode rtcpMode,
             bool enableAudio,
             bool usePCMAudio,
             bool enableVideo,
@@ -149,6 +152,8 @@
     void schedulePullExtractor();
     void onPullExtractor();
 
+    void onSinkFeedback(const sp<AMessage> &msg);
+
     DISALLOW_EVIL_CONSTRUCTORS(PlaybackSession);
 };
 
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.cpp b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
index 72be927..cc8dee3 100644
--- a/media/libstagefright/wifi-display/source/RepeaterSource.cpp
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.cpp
@@ -27,6 +27,25 @@
     CHECK(!mStarted);
 }
 
+double RepeaterSource::getFrameRate() const {
+    return mRateHz;
+}
+
+void RepeaterSource::setFrameRate(double rateHz) {
+    Mutex::Autolock autoLock(mLock);
+
+    if (rateHz == mRateHz) {
+        return;
+    }
+
+    if (mStartTimeUs >= 0ll) {
+        int64_t nextTimeUs = mStartTimeUs + (mFrameCount * 1000000ll) / mRateHz;
+        mStartTimeUs = nextTimeUs;
+        mFrameCount = 0;
+    }
+    mRateHz = rateHz;
+}
+
 status_t RepeaterSource::start(MetaData *params) {
     CHECK(!mStarted);
 
diff --git a/media/libstagefright/wifi-display/source/RepeaterSource.h b/media/libstagefright/wifi-display/source/RepeaterSource.h
index 146af32..8d414fd 100644
--- a/media/libstagefright/wifi-display/source/RepeaterSource.h
+++ b/media/libstagefright/wifi-display/source/RepeaterSource.h
@@ -28,6 +28,9 @@
     // send updates in a while, this is its wakeup call.
     void wakeUp();
 
+    double getFrameRate() const;
+    void setFrameRate(double rateHz);
+
 protected:
     virtual ~RepeaterSource();
 
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
index d993764..2c4a373 100644
--- a/media/libstagefright/wifi-display/source/TSPacketizer.cpp
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -502,16 +502,121 @@
     // reserved = b1
     // the first fragment of "buffer" follows
 
+    // Each transport packet (except for the last one contributing to the PES
+    // payload) must contain a multiple of 16 bytes of payload per HDCP spec.
+    bool alignPayload =
+        (mFlags & (EMIT_HDCP20_DESCRIPTOR | EMIT_HDCP21_DESCRIPTOR));
+
+    /*
+       a) The very first PES transport stream packet contains
+
+       4 bytes of TS header
+       ... padding
+       14 bytes of static PES header
+       PES_private_data_len + 1 bytes (only if PES_private_data_len > 0)
+       numStuffingBytes bytes
+
+       followed by the payload
+
+       b) Subsequent PES transport stream packets contain
+
+       4 bytes of TS header
+       ... padding
+
+       followed by the payload
+    */
+
     size_t PES_packet_length = accessUnit->size() + 8 + numStuffingBytes;
     if (PES_private_data_len > 0) {
         PES_packet_length += PES_private_data_len + 1;
     }
 
-    size_t numTSPackets;
-    if (PES_packet_length <= 178) {
-        numTSPackets = 1;
-    } else {
-        numTSPackets = 1 + ((PES_packet_length - 178) + 183) / 184;
+    size_t numTSPackets = 1;
+
+    {
+        // Make sure the PES header fits into a single TS packet:
+        size_t PES_header_size = 14 + numStuffingBytes;
+        if (PES_private_data_len > 0) {
+            PES_header_size += PES_private_data_len + 1;
+        }
+
+        CHECK_LE(PES_header_size, 188u - 4u);
+
+        size_t sizeAvailableForPayload = 188 - 4 - PES_header_size;
+        size_t numBytesOfPayload = accessUnit->size();
+
+        if (numBytesOfPayload > sizeAvailableForPayload) {
+            numBytesOfPayload = sizeAvailableForPayload;
+
+            if (alignPayload && numBytesOfPayload > 16) {
+                numBytesOfPayload -= (numBytesOfPayload % 16);
+            }
+        }
+
+        // size_t numPaddingBytes = sizeAvailableForPayload - numBytesOfPayload;
+        ALOGV("packet 1 contains %zd padding bytes and %zd bytes of payload",
+              numPaddingBytes, numBytesOfPayload);
+
+        size_t numBytesOfPayloadRemaining = accessUnit->size() - numBytesOfPayload;
+
+#if 0
+        // The following hopefully illustrates the logic that led to the
+        // more efficient computation in the #else block...
+
+        while (numBytesOfPayloadRemaining > 0) {
+            size_t sizeAvailableForPayload = 188 - 4;
+
+            size_t numBytesOfPayload = numBytesOfPayloadRemaining;
+
+            if (numBytesOfPayload > sizeAvailableForPayload) {
+                numBytesOfPayload = sizeAvailableForPayload;
+
+                if (alignPayload && numBytesOfPayload > 16) {
+                    numBytesOfPayload -= (numBytesOfPayload % 16);
+                }
+            }
+
+            size_t numPaddingBytes = sizeAvailableForPayload - numBytesOfPayload;
+            ALOGI("packet %zd contains %zd padding bytes and %zd bytes of payload",
+                    numTSPackets + 1, numPaddingBytes, numBytesOfPayload);
+
+            numBytesOfPayloadRemaining -= numBytesOfPayload;
+            ++numTSPackets;
+        }
+#else
+        // This is how many bytes of payload each subsequent TS packet
+        // can contain at most.
+        sizeAvailableForPayload = 188 - 4;
+        size_t sizeAvailableForAlignedPayload = sizeAvailableForPayload;
+        if (alignPayload) {
+            // We're only going to use a subset of the available space
+            // since we need to make each fragment a multiple of 16 in size.
+            sizeAvailableForAlignedPayload -=
+                (sizeAvailableForAlignedPayload % 16);
+        }
+
+        size_t numFullTSPackets =
+            numBytesOfPayloadRemaining / sizeAvailableForAlignedPayload;
+
+        numTSPackets += numFullTSPackets;
+
+        numBytesOfPayloadRemaining -=
+            numFullTSPackets * sizeAvailableForAlignedPayload;
+
+        // numBytesOfPayloadRemaining < sizeAvailableForAlignedPayload
+        if (numFullTSPackets == 0 && numBytesOfPayloadRemaining > 0) {
+            // There wasn't enough payload left to form a full aligned payload,
+            // the last packet doesn't have to be aligned.
+            ++numTSPackets;
+        } else if (numFullTSPackets > 0
+                && numBytesOfPayloadRemaining
+                    + sizeAvailableForAlignedPayload > sizeAvailableForPayload) {
+            // The last packet emitted had a full aligned payload and together
+            // with the bytes remaining does exceed the unaligned payload
+            // size, so we need another packet.
+            ++numTSPackets;
+        }
+#endif
     }
 
     if (flags & EMIT_PAT_AND_PMT) {
@@ -755,8 +860,6 @@
 
     uint64_t PTS = (timeUs * 9ll) / 100ll;
 
-    bool padding = (PES_packet_length < (188 - 10));
-
     if (PES_packet_length >= 65536) {
         // This really should only happen for video.
         CHECK(track->isVideo());
@@ -765,19 +868,37 @@
         PES_packet_length = 0;
     }
 
+    size_t sizeAvailableForPayload = 188 - 4 - 14 - numStuffingBytes;
+    if (PES_private_data_len > 0) {
+        sizeAvailableForPayload -= PES_private_data_len + 1;
+    }
+
+    size_t copy = accessUnit->size();
+
+    if (copy > sizeAvailableForPayload) {
+        copy = sizeAvailableForPayload;
+
+        if (alignPayload && copy > 16) {
+            copy -= (copy % 16);
+        }
+    }
+
+    size_t numPaddingBytes = sizeAvailableForPayload - copy;
+
     uint8_t *ptr = packetDataStart;
     *ptr++ = 0x47;
     *ptr++ = 0x40 | (track->PID() >> 8);
     *ptr++ = track->PID() & 0xff;
-    *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
 
-    if (padding) {
-        size_t paddingSize = 188 - 10 - PES_packet_length;
-        *ptr++ = paddingSize - 1;
-        if (paddingSize >= 2) {
+    *ptr++ = (numPaddingBytes > 0 ? 0x30 : 0x10)
+                | track->incrementContinuityCounter();
+
+    if (numPaddingBytes > 0) {
+        *ptr++ = numPaddingBytes - 1;
+        if (numPaddingBytes >= 2) {
             *ptr++ = 0x00;
-            memset(ptr, 0xff, paddingSize - 2);
-            ptr += paddingSize - 2;
+            memset(ptr, 0xff, numPaddingBytes - 2);
+            ptr += numPaddingBytes - 2;
         }
     }
 
@@ -813,25 +934,14 @@
         *ptr++ = 0xff;
     }
 
-    // 18 bytes of TS/PES header leave 188 - 18 = 170 bytes for the payload
-
-    size_t sizeLeft = packetDataStart + 188 - ptr;
-    size_t copy = accessUnit->size();
-    if (copy > sizeLeft) {
-        copy = sizeLeft;
-    }
-
     memcpy(ptr, accessUnit->data(), copy);
     ptr += copy;
-    CHECK_EQ(sizeLeft, copy);
-    memset(ptr, 0xff, sizeLeft - copy);
 
+    CHECK_EQ(ptr, packetDataStart + 188);
     packetDataStart += 188;
 
     size_t offset = copy;
     while (offset < accessUnit->size()) {
-        bool padding = (accessUnit->size() - offset) < (188 - 4);
-
         // for subsequent fragments of "buffer":
         // 0x47
         // transport_error_indicator = b0
@@ -843,35 +953,40 @@
         // continuity_counter = b????
         // the fragment of "buffer" follows.
 
+        size_t sizeAvailableForPayload = 188 - 4;
+
+        size_t copy = accessUnit->size() - offset;
+
+        if (copy > sizeAvailableForPayload) {
+            copy = sizeAvailableForPayload;
+
+            if (alignPayload && copy > 16) {
+                copy -= (copy % 16);
+            }
+        }
+
+        size_t numPaddingBytes = sizeAvailableForPayload - copy;
+
         uint8_t *ptr = packetDataStart;
         *ptr++ = 0x47;
         *ptr++ = 0x00 | (track->PID() >> 8);
         *ptr++ = track->PID() & 0xff;
 
-        *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
+        *ptr++ = (numPaddingBytes > 0 ? 0x30 : 0x10)
+                    | track->incrementContinuityCounter();
 
-        if (padding) {
-            size_t paddingSize = 188 - 4 - (accessUnit->size() - offset);
-            *ptr++ = paddingSize - 1;
-            if (paddingSize >= 2) {
+        if (numPaddingBytes > 0) {
+            *ptr++ = numPaddingBytes - 1;
+            if (numPaddingBytes >= 2) {
                 *ptr++ = 0x00;
-                memset(ptr, 0xff, paddingSize - 2);
-                ptr += paddingSize - 2;
+                memset(ptr, 0xff, numPaddingBytes - 2);
+                ptr += numPaddingBytes - 2;
             }
         }
 
-        // 4 bytes of TS header leave 188 - 4 = 184 bytes for the payload
-
-        size_t sizeLeft = packetDataStart + 188 - ptr;
-        size_t copy = accessUnit->size() - offset;
-        if (copy > sizeLeft) {
-            copy = sizeLeft;
-        }
-
         memcpy(ptr, accessUnit->data() + offset, copy);
         ptr += copy;
-        CHECK_EQ(sizeLeft, copy);
-        memset(ptr, 0xff, sizeLeft - copy);
+        CHECK_EQ(ptr, packetDataStart + 188);
 
         offset += copy;
         packetDataStart += 188;
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index 5167cb3..792a9c5 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -34,6 +34,7 @@
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
 #include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/Utils.h>
 
 #include <arpa/inet.h>
 #include <cutils/properties.h>
@@ -42,6 +43,9 @@
 
 namespace android {
 
+// static
+const AString WifiDisplaySource::sUserAgent = MakeUserAgent();
+
 WifiDisplaySource::WifiDisplaySource(
         const sp<ANetworkSession> &netSession,
         const sp<IRemoteDisplayClient> &client,
@@ -1159,7 +1163,7 @@
         return ERROR_MALFORMED;
     }
 
-    RTPSender::TransportMode transportMode = RTPSender::TRANSPORT_UDP;
+    RTPSender::TransportMode rtpMode = RTPSender::TRANSPORT_UDP;
 
     int clientRtp, clientRtcp;
     if (transport.startsWith("RTP/AVP/TCP;")) {
@@ -1168,7 +1172,7 @@
                     transport.c_str(), "interleaved", &interleaved)
                 && sscanf(interleaved.c_str(), "%d-%d",
                           &clientRtp, &clientRtcp) == 2) {
-            transportMode = RTPSender::TRANSPORT_TCP_INTERLEAVED;
+            rtpMode = RTPSender::TRANSPORT_TCP_INTERLEAVED;
         } else {
             bool badRequest = false;
 
@@ -1190,7 +1194,7 @@
                 return ERROR_MALFORMED;
             }
 
-            transportMode = RTPSender::TRANSPORT_TCP;
+            rtpMode = RTPSender::TRANSPORT_TCP;
         }
     } else if (transport.startsWith("RTP/AVP;unicast;")
             || transport.startsWith("RTP/AVP/UDP;unicast;")) {
@@ -1249,11 +1253,17 @@
         return ERROR_MALFORMED;
     }
 
+    RTPSender::TransportMode rtcpMode = RTPSender::TRANSPORT_UDP;
+    if (clientRtcp < 0) {
+        rtcpMode = RTPSender::TRANSPORT_NONE;
+    }
+
     status_t err = playbackSession->init(
             mClientInfo.mRemoteIP.c_str(),
             clientRtp,
+            rtpMode,
             clientRtcp,
-            transportMode,
+            rtcpMode,
             mSinkSupportsAudio,
             mUsingPCMAudio,
             mSinkSupportsVideo,
@@ -1282,7 +1292,7 @@
     AString response = "RTSP/1.0 200 OK\r\n";
     AppendCommonResponse(&response, cseq, playbackSessionID);
 
-    if (transportMode == RTPSender::TRANSPORT_TCP_INTERLEAVED) {
+    if (rtpMode == RTPSender::TRANSPORT_TCP_INTERLEAVED) {
         response.append(
                 StringPrintf(
                     "Transport: RTP/AVP/TCP;interleaved=%d-%d;",
@@ -1291,7 +1301,7 @@
         int32_t serverRtp = playbackSession->getRTPPort();
 
         AString transportString = "UDP";
-        if (transportMode == RTPSender::TRANSPORT_TCP) {
+        if (rtpMode == RTPSender::TRANSPORT_TCP) {
             transportString = "TCP";
         }
 
@@ -1553,7 +1563,7 @@
     response->append(buf);
     response->append("\r\n");
 
-    response->append("Server: Mine/1.0\r\n");
+    response->append(StringPrintf("Server: %s\r\n", sUserAgent.c_str()));
 
     if (cseq >= 0) {
         response->append(StringPrintf("CSeq: %d\r\n", cseq));
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index 3a1b0f9..3efa0b4 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -113,6 +113,8 @@
     static const int64_t kPlaybackSessionTimeoutUs =
         kPlaybackSessionTimeoutSecs * 1000000ll;
 
+    static const AString sUserAgent;
+
     State mState;
     VideoFormats mSupportedSourceVideoFormats;
     sp<ANetworkSession> mNetSession;
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
index 4f7dcc8..9fee4d0 100644
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -43,7 +43,8 @@
             "               -u uri        \tconnect to an rtsp uri\n"
             "               -l ip[:port] \tlisten on the specified port "
             "               -f(ilename)  \tstream media "
-            "(create a sink)\n",
+            "(create a sink)\n"
+            "               -s(pecial)   \trun in 'special' mode\n",
             me);
 }
 
@@ -222,8 +223,10 @@
 
     AString path;
 
+    bool specialMode = false;
+
     int res;
-    while ((res = getopt(argc, argv, "hc:l:u:f:")) >= 0) {
+    while ((res = getopt(argc, argv, "hc:l:u:f:s")) >= 0) {
         switch (res) {
             case 'c':
             {
@@ -281,6 +284,12 @@
                 break;
             }
 
+            case 's':
+            {
+                specialMode = true;
+                break;
+            }
+
             case '?':
             case 'h':
             default:
@@ -357,7 +366,7 @@
     sp<ALooper> looper = new ALooper;
 
     sp<WifiDisplaySink> sink = new WifiDisplaySink(
-            0 /* flags */,
+            specialMode ? WifiDisplaySink::FLAG_SPECIAL_MODE : 0 /* flags */,
             session,
             surface->getIGraphicBufferProducer());
 
diff --git a/services/medialog/MediaLogService.cpp b/services/medialog/MediaLogService.cpp
index 2332b3e..f60749d 100644
--- a/services/medialog/MediaLogService.cpp
+++ b/services/medialog/MediaLogService.cpp
@@ -19,6 +19,7 @@
 
 #include <sys/mman.h>
 #include <utils/Log.h>
+#include <binder/PermissionCache.h>
 #include <media/nbaio/NBLog.h>
 #include <private/android_filesystem_config.h>
 #include "MediaLogService.h"
@@ -55,6 +56,14 @@
 
 status_t MediaLogService::dump(int fd, const Vector<String16>& args)
 {
+    // FIXME merge with similar but not identical code at services/audioflinger/ServiceUtilities.cpp
+    static const String16 sDump("android.permission.DUMP");
+    if (!(IPCThreadState::self()->getCallingUid() == AID_MEDIA ||
+            PermissionCache::checkCallingPermission(sDump))) {
+        fdprintf(fd, "Permission denied.\n");
+        return NO_ERROR;
+    }
+
     Vector<NamedReader> namedReaders;
     {
         Mutex::Autolock _l(mLock);