Merge "Bug fix: MediaPlayer's deselectTrack() for subtitle." into jb-mr1-dev
diff --git a/drm/common/IDrmManagerService.cpp b/drm/common/IDrmManagerService.cpp
index b76572c..0282036 100644
--- a/drm/common/IDrmManagerService.cpp
+++ b/drm/common/IDrmManagerService.cpp
@@ -310,7 +310,13 @@
         const String8 key = keyIt.next();
         data.writeString8(key);
         const String8 value = drmInforequest->get(key);
-        data.writeString8((value == String8("")) ? String8("NULL") : value);
+        if (key == String8("FileDescriptorKey")) {
+            int fd = -1;
+            sscanf(value.string(), "FileDescriptor[%d]", &fd);
+            data.writeFileDescriptor(fd);
+        } else {
+            data.writeString8((value == String8("")) ? String8("NULL") : value);
+        }
     }
 
     remote()->transact(ACQUIRE_DRM_INFO, data, &reply);
@@ -1002,8 +1008,15 @@
         const int size = data.readInt32();
         for (int index = 0; index < size; ++index) {
             const String8 key(data.readString8());
-            const String8 value(data.readString8());
-            drmInfoRequest->put(key, (value == String8("NULL")) ? String8("") : value);
+            if (key == String8("FileDescriptorKey")) {
+                char buffer[16];
+                int fd = data.readFileDescriptor();
+                sprintf(buffer, "%lu", (unsigned long)fd);
+                drmInfoRequest->put(key, String8(buffer));
+            } else {
+                const String8 value(data.readString8());
+                drmInfoRequest->put(key, (value == String8("NULL")) ? String8("") : value);
+            }
         }
 
         DrmInfo* drmInfo = acquireDrmInfo(uniqueId, drmInfoRequest);
@@ -1505,4 +1518,3 @@
         return BBinder::onTransact(code, data, reply, flags);
     }
 }
-
diff --git a/include/media/IMediaPlayerService.h b/include/media/IMediaPlayerService.h
index c4c37b6..7a89135 100644
--- a/include/media/IMediaPlayerService.h
+++ b/include/media/IMediaPlayerService.h
@@ -32,6 +32,7 @@
 namespace android {
 
 struct ICrypto;
+struct IHDCP;
 class IMediaRecorder;
 class IOMX;
 class IRemoteDisplay;
@@ -51,6 +52,7 @@
     virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat) = 0;
     virtual sp<IOMX>            getOMX() = 0;
     virtual sp<ICrypto>         makeCrypto() = 0;
+    virtual sp<IHDCP>           makeHDCP() = 0;
 
     // Connects to a remote display.
     // 'iface' specifies the address of the local interface on which to listen for
@@ -62,12 +64,6 @@
     virtual sp<IRemoteDisplay> listenForRemoteDisplay(const sp<IRemoteDisplayClient>& client,
             const String8& iface) = 0;
 
-    // If iface == NULL, disable remote display, otherwise
-    // iface should be of the form "x.x.x.x:y", i.e. ip address
-    // of the local interface to bind to and the port number
-    // to listen on.
-    virtual status_t enableRemoteDisplay(const char *iface) = 0;
-
     // codecs and audio devices usage tracking for the battery app
     enum BatteryDataBits {
         // tracking audio codec
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index c2ec439..c0a0260 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -21,6 +21,7 @@
 #include <binder/Parcel.h>
 #include <binder/IMemory.h>
 #include <media/ICrypto.h>
+#include <media/IHDCP.h>
 #include <media/IMediaPlayerService.h>
 #include <media/IMediaRecorder.h>
 #include <media/IOMX.h>
@@ -41,7 +42,7 @@
     CREATE_METADATA_RETRIEVER,
     GET_OMX,
     MAKE_CRYPTO,
-    ENABLE_REMOTE_DISPLAY,
+    MAKE_HDCP,
     ADD_BATTERY_DATA,
     PULL_BATTERY_DATA,
     LISTEN_FOR_REMOTE_DISPLAY,
@@ -125,19 +126,11 @@
         return interface_cast<ICrypto>(reply.readStrongBinder());
     }
 
-    virtual status_t enableRemoteDisplay(const char *iface) {
+    virtual sp<IHDCP> makeHDCP() {
         Parcel data, reply;
         data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
-
-        if (iface != NULL) {
-            data.writeInt32(1);
-            data.writeCString(iface);
-        } else {
-            data.writeInt32(0);
-        }
-
-        remote()->transact(ENABLE_REMOTE_DISPLAY, data, &reply);
-        return reply.readInt32();
+        remote()->transact(MAKE_HDCP, data, &reply);
+        return interface_cast<IHDCP>(reply.readStrongBinder());
     }
 
     virtual void addBatteryData(uint32_t params) {
@@ -237,13 +230,10 @@
             reply->writeStrongBinder(crypto->asBinder());
             return NO_ERROR;
         } break;
-        case ENABLE_REMOTE_DISPLAY: {
+        case MAKE_HDCP: {
             CHECK_INTERFACE(IMediaPlayerService, data, reply);
-            const char *iface = NULL;
-            if (data.readInt32()) {
-                iface = data.readCString();
-            }
-            reply->writeInt32(enableRemoteDisplay(iface));
+            sp<IHDCP> hdcp = makeHDCP();
+            reply->writeStrongBinder(hdcp->asBinder());
             return NO_ERROR;
         } break;
         case ADD_BATTERY_DATA: {
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index c7227b0..5b5ed71 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -9,6 +9,7 @@
 LOCAL_SRC_FILES:=               \
     ActivityManager.cpp         \
     Crypto.cpp                  \
+    HDCP.cpp                    \
     MediaPlayerFactory.cpp      \
     MediaPlayerService.cpp      \
     MediaRecorderClient.cpp     \
diff --git a/media/libmediaplayerservice/HDCP.cpp b/media/libmediaplayerservice/HDCP.cpp
index 6f8a465..e7dea6e 100644
--- a/media/libmediaplayerservice/HDCP.cpp
+++ b/media/libmediaplayerservice/HDCP.cpp
@@ -36,13 +36,16 @@
         return;
     }
 
-    typedef HDCPModule *(*CreateHDCPModuleFunc)();
+    typedef HDCPModule *(*CreateHDCPModuleFunc)(
+            void *, HDCPModule::ObserverFunc);
+
     CreateHDCPModuleFunc createHDCPModule =
         (CreateHDCPModuleFunc)dlsym(mLibHandle, "createHDCPModule");
 
     if (createHDCPModule == NULL) {
         ALOGE("Unable to find symbol 'createHDCPModule'.");
-    } else if ((mHDCPModule = createHDCPModule()) == NULL) {
+    } else if ((mHDCPModule = createHDCPModule(
+                    this, &HDCP::ObserveWrapper)) == NULL) {
         ALOGE("createHDCPModule failed.");
     }
 }
@@ -97,5 +100,16 @@
     return mHDCPModule->encrypt(inData, size, streamCTR, outInputCTR, outData);
 }
 
+// static
+void HDCP::ObserveWrapper(void *me, int msg, int ext1, int ext2) {
+    static_cast<HDCP *>(me)->observe(msg, ext1, ext2);
+}
+
+void HDCP::observe(int msg, int ext1, int ext2) {
+    if (mObserver != NULL) {
+        mObserver->notify(msg, ext1, ext2, NULL /* obj */);
+    }
+}
+
 }  // namespace android
 
diff --git a/media/libmediaplayerservice/HDCP.h b/media/libmediaplayerservice/HDCP.h
index 2e27689..4ee664d 100644
--- a/media/libmediaplayerservice/HDCP.h
+++ b/media/libmediaplayerservice/HDCP.h
@@ -39,6 +39,9 @@
     HDCPModule *mHDCPModule;
     sp<IHDCPObserver> mObserver;
 
+    static void ObserveWrapper(void *me, int msg, int ext1, int ext2);
+    void observe(int msg, int ext1, int ext2);
+
     DISALLOW_EVIL_CONSTRUCTORS(HDCP);
 };
 
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 1be4edf..9bedff1 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -72,6 +72,7 @@
 #include <OMX.h>
 
 #include "Crypto.h"
+#include "HDCP.h"
 #include "RemoteDisplay.h"
 
 namespace {
@@ -281,6 +282,10 @@
     return new Crypto;
 }
 
+sp<IHDCP> MediaPlayerService::makeHDCP() {
+    return new HDCP;
+}
+
 sp<IRemoteDisplay> MediaPlayerService::listenForRemoteDisplay(
         const sp<IRemoteDisplayClient>& client, const String8& iface) {
     if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
@@ -290,31 +295,6 @@
     return new RemoteDisplay(client, iface.string());
 }
 
-// TODO: delete this method
-status_t MediaPlayerService::enableRemoteDisplay(const char *iface) {
-    if (!checkPermission("android.permission.CONTROL_WIFI_DISPLAY")) {
-        return PERMISSION_DENIED;
-    }
-
-    Mutex::Autolock autoLock(mLock);
-
-    if (iface != NULL) {
-        if (mRemoteDisplay != NULL) {
-            return INVALID_OPERATION;
-        }
-
-        mRemoteDisplay = new RemoteDisplay(NULL /* client */, iface);
-        return OK;
-    }
-
-    if (mRemoteDisplay != NULL) {
-        mRemoteDisplay->dispose();
-        mRemoteDisplay.clear();
-    }
-
-    return OK;
-}
-
 status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
 {
     const size_t SIZE = 256;
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index ca8a96f..fd648df 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -44,7 +44,6 @@
 class IRemoteDisplay;
 class IRemoteDisplayClient;
 class MediaRecorderClient;
-struct RemoteDisplay;
 
 #define CALLBACK_ANTAGONIZER 0
 #if CALLBACK_ANTAGONIZER
@@ -250,11 +249,10 @@
     virtual sp<IMemory>         decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, audio_format_t* pFormat);
     virtual sp<IOMX>            getOMX();
     virtual sp<ICrypto>         makeCrypto();
+    virtual sp<IHDCP>           makeHDCP();
 
     virtual sp<IRemoteDisplay> listenForRemoteDisplay(const sp<IRemoteDisplayClient>& client,
             const String8& iface);
-    virtual status_t            enableRemoteDisplay(const char *iface);
-
     virtual status_t            dump(int fd, const Vector<String16>& args);
 
             void                removeClient(wp<Client> client);
@@ -430,7 +428,6 @@
                 int32_t                     mNextConnId;
                 sp<IOMX>                    mOMX;
                 sp<ICrypto>                 mCrypto;
-                sp<RemoteDisplay>           mRemoteDisplay;
 };
 
 // ----------------------------------------------------------------------------
diff --git a/media/libstagefright/wifi-display/Android.mk b/media/libstagefright/wifi-display/Android.mk
index 681ba4f..f8e4219 100644
--- a/media/libstagefright/wifi-display/Android.mk
+++ b/media/libstagefright/wifi-display/Android.mk
@@ -4,6 +4,7 @@
 
 LOCAL_SRC_FILES:= \
         ANetworkSession.cpp             \
+        Parameters.cpp                  \
         ParsedMessage.cpp               \
         sink/LinearRegression.cpp       \
         sink/RTPSink.cpp                \
@@ -13,7 +14,6 @@
         source/MediaPuller.cpp          \
         source/PlaybackSession.cpp      \
         source/RepeaterSource.cpp       \
-        source/Serializer.cpp           \
         source/TSPacketizer.cpp         \
         source/WifiDisplaySource.cpp    \
 
diff --git a/media/libstagefright/wifi-display/Parameters.cpp b/media/libstagefright/wifi-display/Parameters.cpp
new file mode 100644
index 0000000..f7118b3
--- /dev/null
+++ b/media/libstagefright/wifi-display/Parameters.cpp
@@ -0,0 +1,90 @@
+/*
+ * 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.
+ */
+
+#include "Parameters.h"
+
+#include <media/stagefright/MediaErrors.h>
+
+namespace android {
+
+// static
+sp<Parameters> Parameters::Parse(const char *data, size_t size) {
+    sp<Parameters> params = new Parameters;
+    status_t err = params->parse(data, size);
+
+    if (err != OK) {
+        return NULL;
+    }
+
+    return params;
+}
+
+Parameters::Parameters() {}
+
+Parameters::~Parameters() {}
+
+status_t Parameters::parse(const char *data, size_t size) {
+    size_t i = 0;
+    while (i < size) {
+        size_t nameStart = i;
+        while (i < size && data[i] != ':') {
+            ++i;
+        }
+
+        if (i == size || i == nameStart) {
+            return ERROR_MALFORMED;
+        }
+
+        AString name(&data[nameStart], i - nameStart);
+        name.trim();
+        name.tolower();
+
+        ++i;
+
+        size_t valueStart = i;
+
+        while (i + 1 < size && (data[i] != '\r' || data[i + 1] != '\n')) {
+            ++i;
+        }
+
+        AString value(&data[valueStart], i - valueStart);
+        value.trim();
+
+        mDict.add(name, value);
+
+        i += 2;
+    }
+
+    return OK;
+}
+
+bool Parameters::findParameter(const char *name, AString *value) const {
+    AString key = name;
+    key.tolower();
+
+    ssize_t index = mDict.indexOfKey(key);
+
+    if (index < 0) {
+        value->clear();
+
+        return false;
+    }
+
+    *value = mDict.valueAt(index);
+    return true;
+}
+
+}  // namespace android
diff --git a/media/libstagefright/wifi-display/Parameters.h b/media/libstagefright/wifi-display/Parameters.h
new file mode 100644
index 0000000..a5e787e
--- /dev/null
+++ b/media/libstagefright/wifi-display/Parameters.h
@@ -0,0 +1,41 @@
+/*
+ * 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.
+ */
+
+#include <media/stagefright/foundation/ABase.h>
+#include <media/stagefright/foundation/AString.h>
+#include <utils/KeyedVector.h>
+#include <utils/RefBase.h>
+
+namespace android {
+
+struct Parameters : public RefBase {
+    static sp<Parameters> Parse(const char *data, size_t size);
+
+    bool findParameter(const char *name, AString *value) const;
+
+protected:
+    virtual ~Parameters();
+
+private:
+    KeyedVector<AString, AString> mDict;
+
+    Parameters();
+    status_t parse(const char *data, size_t size);
+
+    DISALLOW_EVIL_CONSTRUCTORS(Parameters);
+};
+
+}  // namespace android
diff --git a/media/libstagefright/wifi-display/source/Converter.cpp b/media/libstagefright/wifi-display/source/Converter.cpp
index 390b2e2..c5c47d6 100644
--- a/media/libstagefright/wifi-display/source/Converter.cpp
+++ b/media/libstagefright/wifi-display/source/Converter.cpp
@@ -115,7 +115,7 @@
     mOutputFormat->setString("mime", outputMIME.c_str());
 
     int32_t audioBitrate = getBitrate("media.wfd.audio-bitrate", 64000);
-    int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 10000000);
+    int32_t videoBitrate = getBitrate("media.wfd.video-bitrate", 2500000);
 
     ALOGI("using audio bitrate of %d bps, video bitrate of %d bps",
           audioBitrate, videoBitrate);
@@ -328,4 +328,3 @@
 }
 
 }  // namespace android
-
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.cpp b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
index e961518..24f33df 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.cpp
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.cpp
@@ -23,12 +23,12 @@
 #include "Converter.h"
 #include "MediaPuller.h"
 #include "RepeaterSource.h"
-#include "Serializer.h"
 #include "TSPacketizer.h"
 
 #include <binder/IServiceManager.h>
 #include <gui/ISurfaceComposer.h>
 #include <gui/SurfaceComposerClient.h>
+#include <media/IHDCP.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
 #include <media/stagefright/foundation/AMessage.h>
@@ -46,9 +46,6 @@
 
 #include <OMX_IVCommon.h>
 
-//#define FAKE_VIDEO            1
-#define USE_SERIALIZER          0
-
 namespace android {
 
 static size_t kMaxRTPPacketSize = 1500;
@@ -63,6 +60,7 @@
     Track(const sp<AMessage> &format);
 
     sp<AMessage> getFormat();
+    bool isAudio() const;
 
     const sp<Converter> &converter() const;
     ssize_t packetizerTrackIndex() const;
@@ -83,6 +81,9 @@
     sp<AMessage> mFormat;
     bool mStarted;
     ssize_t mPacketizerTrackIndex;
+    bool mIsAudio;
+
+    static bool IsAudioFormat(const sp<AMessage> &format);
 
     DISALLOW_EVIL_CONSTRUCTORS(Track);
 };
@@ -97,18 +98,29 @@
       mMediaPuller(mediaPuller),
       mConverter(converter),
       mStarted(false),
-      mPacketizerTrackIndex(-1) {
+      mPacketizerTrackIndex(-1),
+      mIsAudio(IsAudioFormat(mConverter->getOutputFormat())) {
 }
 
 WifiDisplaySource::PlaybackSession::Track::Track(const sp<AMessage> &format)
     : mFormat(format),
-      mPacketizerTrackIndex(-1) {
+      mPacketizerTrackIndex(-1),
+      mIsAudio(IsAudioFormat(mFormat)) {
 }
 
 WifiDisplaySource::PlaybackSession::Track::~Track() {
     stop();
 }
 
+// static
+bool WifiDisplaySource::PlaybackSession::Track::IsAudioFormat(
+        const sp<AMessage> &format) {
+    AString mime;
+    CHECK(format->findString("mime", &mime));
+
+    return !strncasecmp(mime.c_str(), "audio/", 6);
+}
+
 sp<AMessage> WifiDisplaySource::PlaybackSession::Track::getFormat() {
     if (mFormat != NULL) {
         return mFormat;
@@ -117,6 +129,10 @@
     return mConverter->getOutputFormat();
 }
 
+bool WifiDisplaySource::PlaybackSession::Track::isAudio() const {
+    return mIsAudio;
+}
+
 const sp<Converter> &WifiDisplaySource::PlaybackSession::Track::converter() const {
     return mConverter;
 }
@@ -172,11 +188,11 @@
         const sp<ANetworkSession> &netSession,
         const sp<AMessage> &notify,
         const in_addr &interfaceAddr,
-        bool legacyMode)
+        const sp<IHDCP> &hdcp)
     : mNetSession(netSession),
       mNotify(notify),
       mInterfaceAddr(interfaceAddr),
-      mLegacyMode(legacyMode),
+      mHDCP(hdcp),
       mLastLifesignUs(),
       mVideoTrackIndex(-1),
       mTSQueue(new ABuffer(12 + kMaxNumTSPacketsPerRTPPacket * 188)),
@@ -187,11 +203,18 @@
       mRTPPort(0),
       mRTPSessionID(0),
       mRTCPSessionID(0),
+#if ENABLE_RETRANSMISSION
+      mRTPRetransmissionSessionID(0),
+      mRTCPRetransmissionSessionID(0),
+#endif
       mClientRTPPort(0),
       mClientRTCPPort(0),
       mRTPConnected(false),
       mRTCPConnected(false),
       mRTPSeqNo(0),
+#if ENABLE_RETRANSMISSION
+      mRTPRetransmissionSeqNo(0),
+#endif
       mLastNTPTime(0),
       mLastRTPTime(0),
       mNumRTPSent(0),
@@ -257,6 +280,15 @@
 
     sp<AMessage> rtpNotify = new AMessage(kWhatRTPNotify, id());
     sp<AMessage> rtcpNotify = new AMessage(kWhatRTCPNotify, id());
+
+#if ENABLE_RETRANSMISSION
+    sp<AMessage> rtpRetransmissionNotify =
+        new AMessage(kWhatRTPRetransmissionNotify, id());
+
+    sp<AMessage> rtcpRetransmissionNotify =
+        new AMessage(kWhatRTCPRetransmissionNotify, id());
+#endif
+
     for (serverRtp = 15550;; serverRtp += 2) {
         int32_t rtpSession;
         if (mTransportMode == TRANSPORT_UDP) {
@@ -274,39 +306,76 @@
             continue;
         }
 
-        if (clientRtcp < 0) {
-            // No RTCP.
+        int32_t rtcpSession = 0;
 
-            mRTPPort = serverRtp;
-            mRTPSessionID = rtpSession;
-            mRTCPSessionID = 0;
+        if (clientRtcp >= 0) {
+            if (mTransportMode == TRANSPORT_UDP) {
+                err = mNetSession->createUDPSession(
+                        serverRtp + 1, clientIP, clientRtcp,
+                        rtcpNotify, &rtcpSession);
+            } else {
+                err = mNetSession->createTCPDatagramSession(
+                        serverRtp + 1, clientIP, clientRtcp,
+                        rtcpNotify, &rtcpSession);
+            }
 
-            ALOGI("rtpSessionId = %d", rtpSession);
-            break;
+            if (err != OK) {
+                ALOGI("failed to create RTCP socket on port %d", serverRtp + 1);
+
+                mNetSession->destroySession(rtpSession);
+                continue;
+            }
         }
 
-        int32_t rtcpSession;
+#if ENABLE_RETRANSMISSION
         if (mTransportMode == TRANSPORT_UDP) {
+            int32_t rtpRetransmissionSession;
+
             err = mNetSession->createUDPSession(
-                    serverRtp + 1, clientIP, clientRtcp,
-                    rtcpNotify, &rtcpSession);
-        } else {
-            err = mNetSession->createTCPDatagramSession(
-                    serverRtp + 1, clientIP, clientRtcp,
-                    rtcpNotify, &rtcpSession);
+                        serverRtp + kRetransmissionPortOffset,
+                        clientIP,
+                        clientRtp + kRetransmissionPortOffset,
+                        rtpRetransmissionNotify,
+                        &rtpRetransmissionSession);
+
+            if (err != OK) {
+                mNetSession->destroySession(rtcpSession);
+                mNetSession->destroySession(rtpSession);
+                continue;
+            }
+
+            CHECK_GE(clientRtcp, 0);
+
+            int32_t rtcpRetransmissionSession;
+            err = mNetSession->createUDPSession(
+                        serverRtp + 1 + kRetransmissionPortOffset,
+                        clientIP,
+                        clientRtp + 1 + kRetransmissionPortOffset,
+                        rtcpRetransmissionNotify,
+                        &rtcpRetransmissionSession);
+
+            if (err != OK) {
+                mNetSession->destroySession(rtpRetransmissionSession);
+                mNetSession->destroySession(rtcpSession);
+                mNetSession->destroySession(rtpSession);
+                continue;
+            }
+
+            mRTPRetransmissionSessionID = rtpRetransmissionSession;
+            mRTCPRetransmissionSessionID = rtcpRetransmissionSession;
+
+            ALOGI("rtpRetransmissionSessionID = %d, "
+                  "rtcpRetransmissionSessionID = %d",
+                  rtpRetransmissionSession, rtcpRetransmissionSession);
         }
+#endif
 
-        if (err == OK) {
-            mRTPPort = serverRtp;
-            mRTPSessionID = rtpSession;
-            mRTCPSessionID = rtcpSession;
+        mRTPPort = serverRtp;
+        mRTPSessionID = rtpSession;
+        mRTCPSessionID = rtcpSession;
 
-            ALOGI("rtpSessionID = %d, rtcpSessionID = %d", rtpSession, rtcpSession);
-            break;
-        }
-
-        ALOGI("failed to create RTCP socket on port %d", serverRtp + 1);
-        mNetSession->destroySession(rtpSession);
+        ALOGI("rtpSessionID = %d, rtcpSessionID = %d", rtpSession, rtcpSession);
+        break;
     }
 
     if (mRTPPort == 0) {
@@ -382,10 +451,6 @@
         scheduleSendSR();
     }
 
-    if (mSerializer != NULL) {
-        return mSerializer->start();
-    }
-
     for (size_t i = 0; i < mTracks.size(); ++i) {
         status_t err = mTracks.editValueAt(i)->start();
 
@@ -416,28 +481,17 @@
 
     mPacketizer.clear();
 
-    if (mSerializer != NULL) {
-        mSerializer->stop();
-
-        looper()->unregisterHandler(mSerializer->id());
-        mSerializer.clear();
-    }
-
     mTracks.clear();
 
-    if (mSerializerLooper != NULL) {
-        mSerializerLooper->stop();
-        mSerializerLooper.clear();
+#if ENABLE_RETRANSMISSION
+    if (mRTCPRetransmissionSessionID != 0) {
+        mNetSession->destroySession(mRTCPRetransmissionSessionID);
     }
 
-    if (mLegacyMode) {
-        sp<IServiceManager> sm = defaultServiceManager();
-        sp<IBinder> binder = sm->getService(String16("SurfaceFlinger"));
-        sp<ISurfaceComposer> service = interface_cast<ISurfaceComposer>(binder);
-        CHECK(service != NULL);
-
-        service->connectDisplay(NULL);
+    if (mRTPRetransmissionSessionID != 0) {
+        mNetSession->destroySession(mRTPRetransmissionSessionID);
     }
+#endif
 
     if (mRTCPSessionID != 0) {
         mNetSession->destroySession(mRTCPSessionID);
@@ -455,6 +509,10 @@
     switch (msg->what()) {
         case kWhatRTPNotify:
         case kWhatRTCPNotify:
+#if ENABLE_RETRANSMISSION
+        case kWhatRTPRetransmissionNotify:
+        case kWhatRTCPRetransmissionNotify:
+#endif
         {
             int32_t reason;
             CHECK(msg->findInt32("reason", &reason));
@@ -474,8 +532,11 @@
                     AString detail;
                     CHECK(msg->findString("detail", &detail));
 
-                    if (msg->what() == kWhatRTPNotify
-                            && !errorOccuredDuringSend) {
+                    if ((msg->what() == kWhatRTPNotify
+#if ENABLE_RETRANSMISSION
+                            || msg->what() == kWhatRTPRetransmissionNotify
+#endif
+                        ) && !errorOccuredDuringSend) {
                         // This is ok, we don't expect to receive anything on
                         // the RTP socket.
                         break;
@@ -496,6 +557,13 @@
                     } else if (sessionID == mRTCPSessionID) {
                         mRTCPSessionID = 0;
                     }
+#if ENABLE_RETRANSMISSION
+                    else if (sessionID == mRTPRetransmissionSessionID) {
+                        mRTPRetransmissionSessionID = 0;
+                    } else if (sessionID == mRTCPRetransmissionSessionID) {
+                        mRTCPRetransmissionSessionID = 0;
+                    }
+#endif
 
                     // Inform WifiDisplaySource of our premature death (wish).
                     sp<AMessage> notify = mNotify->dup();
@@ -513,7 +581,12 @@
                     CHECK(msg->findBuffer("data", &data));
 
                     status_t err;
-                    if (msg->what() == kWhatRTCPNotify) {
+                    if (msg->what() == kWhatRTCPNotify
+#if ENABLE_RETRANSMISSION
+                            || msg->what() == kWhatRTCPRetransmissionNotify
+#endif
+                       )
+                    {
                         err = parseRTCP(data);
                     }
                     break;
@@ -565,26 +638,19 @@
             break;
         }
 
-        case kWhatSerializerNotify:
+        case kWhatMediaPullerNotify:
         {
             int32_t what;
             CHECK(msg->findInt32("what", &what));
 
-            if (what == Serializer::kWhatEOS) {
+            if (what == MediaPuller::kWhatEOS) {
                 ALOGI("input eos");
 
                 for (size_t i = 0; i < mTracks.size(); ++i) {
-#if FAKE_VIDEO
-                    sp<AMessage> msg = new AMessage(kWhatConverterNotify, id());
-                    msg->setInt32("what", Converter::kWhatEOS);
-                    msg->setSize("trackIndex", i);
-                    msg->post();
-#else
                     mTracks.valueAt(i)->converter()->signalEOS();
-#endif
                 }
             } else {
-                CHECK_EQ(what, Serializer::kWhatAccessUnit);
+                CHECK_EQ(what, MediaPuller::kWhatAccessUnit);
 
                 size_t trackIndex;
                 CHECK(msg->findSize("trackIndex", &trackIndex));
@@ -592,25 +658,8 @@
                 sp<ABuffer> accessUnit;
                 CHECK(msg->findBuffer("accessUnit", &accessUnit));
 
-#if FAKE_VIDEO
-                int64_t timeUs;
-                CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
-
-                void *mbuf;
-                CHECK(accessUnit->meta()->findPointer("mediaBuffer", &mbuf));
-
-                ((MediaBuffer *)mbuf)->release();
-                mbuf = NULL;
-
-                sp<AMessage> msg = new AMessage(kWhatConverterNotify, id());
-                msg->setInt32("what", Converter::kWhatAccessUnit);
-                msg->setSize("trackIndex", trackIndex);
-                msg->setBuffer("accessUnit", accessUnit);
-                msg->post();
-#else
                 mTracks.valueFor(trackIndex)->converter()
                     ->feedAccessUnit(accessUnit);
-#endif
             }
             break;
         }
@@ -644,6 +693,73 @@
                     sp<ABuffer> accessUnit;
                     CHECK(msg->findBuffer("accessUnit", &accessUnit));
 
+                    bool isHDCPEncrypted = false;
+                    uint64_t inputCTR;
+                    uint8_t HDCP_private_data[16];
+                    if (mHDCP != NULL && !track->isAudio()) {
+                        isHDCPEncrypted = true;
+
+                        status_t err = mHDCP->encrypt(
+                                accessUnit->data(), accessUnit->size(),
+                                trackIndex  /* streamCTR */,
+                                &inputCTR,
+                                accessUnit->data());
+
+                        if (err != OK) {
+                            ALOGI("Failed to HDCP-encrypt media data (err %d)",
+                                  err);
+
+                            // Inform WifiDisplaySource of our premature death
+                            // (wish).
+                            sp<AMessage> notify = mNotify->dup();
+                            notify->setInt32("what", kWhatSessionDead);
+                            notify->post();
+                            break;
+                        }
+
+                        HDCP_private_data[0] = 0x00;
+
+                        HDCP_private_data[1] =
+                            (((trackIndex >> 30) & 3) << 1) | 1;
+
+                        HDCP_private_data[2] = (trackIndex >> 22) & 0xff;
+
+                        HDCP_private_data[3] =
+                            (((trackIndex >> 15) & 0x7f) << 1) | 1;
+
+                        HDCP_private_data[4] = (trackIndex >> 7) & 0xff;
+
+                        HDCP_private_data[5] =
+                            ((trackIndex & 0x7f) << 1) | 1;
+
+                        HDCP_private_data[6] = 0x00;
+
+                        HDCP_private_data[7] =
+                            (((inputCTR >> 60) & 0x0f) << 1) | 1;
+
+                        HDCP_private_data[8] = (inputCTR >> 52) & 0xff;
+
+                        HDCP_private_data[9] =
+                            (((inputCTR >> 45) & 0x7f) << 1) | 1;
+
+                        HDCP_private_data[10] = (inputCTR >> 37) & 0xff;
+
+                        HDCP_private_data[11] =
+                            (((inputCTR >> 30) & 0x7f) << 1) | 1;
+
+                        HDCP_private_data[12] = (inputCTR >> 22) & 0xff;
+
+                        HDCP_private_data[13] =
+                            (((inputCTR >> 15) & 0x7f) << 1) | 1;
+
+                        HDCP_private_data[14] = (inputCTR >> 7) & 0xff;
+
+                        HDCP_private_data[15] =
+                            ((inputCTR & 0x7f) << 1) | 1;
+
+                        flags |= TSPacketizer::IS_ENCRYPTED;
+                    }
+
                     int64_t timeUs;
                     CHECK(accessUnit->meta()->findInt64("timeUs", &timeUs));
 
@@ -654,7 +770,9 @@
 
                     sp<ABuffer> packets;
                     mPacketizer->packetize(
-                            packetizerTrackIndex, accessUnit, &packets, flags);
+                            packetizerTrackIndex, accessUnit, &packets, flags,
+                            isHDCPEncrypted ? NULL : HDCP_private_data,
+                            isHDCPEncrypted ? 0 : sizeof(HDCP_private_data));
 
                     for (size_t offset = 0;
                             offset < packets->size(); offset += 188) {
@@ -688,11 +806,9 @@
                 ssize_t index = mTracks.indexOfKey(trackIndex);
                 CHECK_GE(index, 0);
 
-#if !FAKE_VIDEO
                 const sp<Converter> &converter =
                     mTracks.valueAt(index)->converter();
                 looper()->unregisterHandler(converter->id());
-#endif
 
                 mTracks.removeItemsAt(index);
 
@@ -722,13 +838,8 @@
 }
 
 status_t WifiDisplaySource::PlaybackSession::setupPacketizer() {
-    sp<AMessage> msg = new AMessage(kWhatSerializerNotify, id());
-
     mPacketizer = new TSPacketizer;
 
-#if FAKE_VIDEO
-    return addFakeSources();
-#else
     status_t err = addVideoSource();
 
     if (err != OK) {
@@ -736,85 +847,10 @@
     }
 
     return addAudioSource();
-#endif
-}
-
-status_t WifiDisplaySource::PlaybackSession::addFakeSources() {
-#if FAKE_VIDEO
-    mSerializerLooper = new ALooper;
-    mSerializerLooper->setName("serializer_looper");
-    mSerializerLooper->start();
-
-    sp<AMessage> msg = new AMessage(kWhatSerializerNotify, id());
-    mSerializer = new Serializer(
-            true /* throttled */, msg);
-
-    mSerializerLooper->registerHandler(mSerializer);
-
-    DataSource::RegisterDefaultSniffers();
-
-    sp<DataSource> dataSource =
-        DataSource::CreateFromURI(
-                "/system/etc/inception_1500.mp4");
-
-    CHECK(dataSource != NULL);
-
-    sp<MediaExtractor> extractor = MediaExtractor::Create(dataSource);
-    CHECK(extractor != NULL);
-
-    bool haveAudio = false;
-    bool haveVideo = false;
-    for (size_t i = 0; i < extractor->countTracks(); ++i) {
-        sp<MetaData> meta = extractor->getTrackMetaData(i);
-
-        const char *mime;
-        CHECK(meta->findCString(kKeyMIMEType, &mime));
-
-        bool useTrack = false;
-        if (!strncasecmp(mime, "audio/", 6) && !haveAudio) {
-            useTrack = true;
-            haveAudio = true;
-        } else if (!strncasecmp(mime, "video/", 6) && !haveVideo) {
-            useTrack = true;
-            haveVideo = true;
-        }
-
-        if (!useTrack) {
-            continue;
-        }
-
-        sp<MediaSource> source = extractor->getTrack(i);
-
-        ssize_t index = mSerializer->addSource(source);
-        CHECK_GE(index, 0);
-
-        sp<AMessage> format;
-        status_t err = convertMetaDataToMessage(source->getFormat(), &format);
-        CHECK_EQ(err, (status_t)OK);
-
-        mTracks.add(index, new Track(format));
-    }
-    CHECK(haveAudio || haveVideo);
-#endif
-
-    return OK;
 }
 
 status_t WifiDisplaySource::PlaybackSession::addSource(
         bool isVideo, const sp<MediaSource> &source, size_t *numInputBuffers) {
-#if USE_SERIALIZER
-    if (mSerializer == NULL) {
-        mSerializerLooper = new ALooper;
-        mSerializerLooper->setName("serializer_looper");
-        mSerializerLooper->start();
-
-        sp<AMessage> msg = new AMessage(kWhatSerializerNotify, id());
-        mSerializer = new Serializer(
-                false /* throttled */, msg);
-
-        mSerializerLooper->registerHandler(mSerializer);
-    }
-#else
     sp<ALooper> pullLooper = new ALooper;
     pullLooper->setName("pull_looper");
 
@@ -822,7 +858,6 @@
             false /* runOnCallingThread */,
             false /* canCallJava */,
             PRIORITY_DEFAULT);
-#endif
 
     sp<ALooper> codecLooper = new ALooper;
     codecLooper->setName("codec_looper");
@@ -836,16 +871,12 @@
 
     sp<AMessage> notify;
 
-#if USE_SERIALIZER
-    trackIndex = mSerializer->addSource(source);
-#else
     trackIndex = mTracks.size();
 
-    notify = new AMessage(kWhatSerializerNotify, id());
+    notify = new AMessage(kWhatMediaPullerNotify, id());
     notify->setSize("trackIndex", trackIndex);
     sp<MediaPuller> puller = new MediaPuller(source, notify);
     pullLooper->registerHandler(puller);
-#endif
 
     sp<AMessage> format;
     status_t err = convertMetaDataToMessage(source->getFormat(), &format);
@@ -871,11 +902,7 @@
         *numInputBuffers = converter->getInputBufferCount();
     }
 
-#if USE_SERIALIZER
-    mTracks.add(trackIndex, new Track(NULL, codecLooper, NULL, converter));
-#else
     mTracks.add(trackIndex, new Track(pullLooper, codecLooper, puller, converter));
-#endif
 
     if (isVideo) {
         mVideoTrackIndex = trackIndex;
@@ -897,21 +924,11 @@
         return err;
     }
 
-    // Add one reference to account for the serializer.
     err = source->setMaxAcquiredBufferCount(numInputBuffers);
     CHECK_EQ(err, (status_t)OK);
 
     mBufferQueue = source->getBufferQueue();
 
-    if (mLegacyMode) {
-        sp<IServiceManager> sm = defaultServiceManager();
-        sp<IBinder> binder = sm->getService(String16("SurfaceFlinger"));
-        sp<ISurfaceComposer> service = interface_cast<ISurfaceComposer>(binder);
-        CHECK(service != NULL);
-
-        service->connectDisplay(mBufferQueue);
-    }
-
     return OK;
 }
 
@@ -938,11 +955,11 @@
 }
 
 int32_t WifiDisplaySource::PlaybackSession::width() const {
-    return mLegacyMode ? 720 : 1280;
+    return 1280;
 }
 
 int32_t WifiDisplaySource::PlaybackSession::height() const {
-    return mLegacyMode ? 1280 : 720;
+    return 720;
 }
 
 void WifiDisplaySource::PlaybackSession::scheduleSendSR() {
@@ -1202,9 +1219,11 @@
             case 204:  // APP
                 break;
 
+#if ENABLE_RETRANSMISSION
             case 205:  // TSFB (transport layer specific feedback)
                 parseTSFB(data, headerLength);
                 break;
+#endif
 
             case 206:  // PSFB (payload specific feedback)
                 hexdump(data, headerLength);
@@ -1225,6 +1244,7 @@
     return OK;
 }
 
+#if ENABLE_RETRANSMISSION
 status_t WifiDisplaySource::PlaybackSession::parseTSFB(
         const uint8_t *data, size_t size) {
     if ((data[0] & 0x1f) != 1) {
@@ -1241,31 +1261,64 @@
         uint16_t blp = U16_AT(&data[i + 2]);
 
         List<sp<ABuffer> >::iterator it = mHistory.begin();
-        bool found = false;
+        bool foundSeqNo = false;
         while (it != mHistory.end()) {
             const sp<ABuffer> &buffer = *it;
 
             uint16_t bufferSeqNo = buffer->int32Data() & 0xffff;
 
+            bool retransmit = false;
             if (bufferSeqNo == seqNo) {
-                sendPacket(mRTPSessionID, buffer->data(), buffer->size());
+                retransmit = true;
+            } else if (blp != 0) {
+                for (size_t i = 0; i < 16; ++i) {
+                    if ((blp & (1 << i))
+                        && (bufferSeqNo == ((seqNo + i + 1) & 0xffff))) {
+                        blp &= ~(1 << i);
+                        retransmit = true;
+                    }
+                }
+            }
 
-                found = true;
-                break;
+            if (retransmit) {
+                ALOGI("retransmitting seqNo %d", bufferSeqNo);
+
+                sp<ABuffer> retransRTP = new ABuffer(2 + buffer->size());
+                uint8_t *rtp = retransRTP->data();
+                memcpy(rtp, buffer->data(), 12);
+                rtp[2] = (mRTPRetransmissionSeqNo >> 8) & 0xff;
+                rtp[3] = mRTPRetransmissionSeqNo & 0xff;
+                rtp[12] = (bufferSeqNo >> 8) & 0xff;
+                rtp[13] = bufferSeqNo & 0xff;
+                memcpy(&rtp[14], buffer->data() + 12, buffer->size() - 12);
+
+                ++mRTPRetransmissionSeqNo;
+
+                sendPacket(
+                        mRTPRetransmissionSessionID,
+                        retransRTP->data(), retransRTP->size());
+
+                if (bufferSeqNo == seqNo) {
+                    foundSeqNo = true;
+                }
+
+                if (foundSeqNo && blp == 0) {
+                    break;
+                }
             }
 
             ++it;
         }
 
-        if (found) {
-            ALOGI("retransmitting seqNo %d", seqNo);
-        } else {
-            ALOGI("seqNo %d no longer available", seqNo);
+        if (!foundSeqNo || blp != 0) {
+            ALOGI("Some sequence numbers were no longer available for "
+                  "retransmission");
         }
     }
 
     return OK;
 }
+#endif
 
 void WifiDisplaySource::PlaybackSession::requestIDRFrame() {
     for (size_t i = 0; i < mTracks.size(); ++i) {
diff --git a/media/libstagefright/wifi-display/source/PlaybackSession.h b/media/libstagefright/wifi-display/source/PlaybackSession.h
index 0047842..f2090b4 100644
--- a/media/libstagefright/wifi-display/source/PlaybackSession.h
+++ b/media/libstagefright/wifi-display/source/PlaybackSession.h
@@ -24,13 +24,14 @@
 
 struct ABuffer;
 struct BufferQueue;
+struct IHDCP;
 struct ISurfaceTexture;
 struct MediaPuller;
 struct MediaSource;
-struct Serializer;
 struct TSPacketizer;
 
-#define LOG_TRANSPORT_STREAM    0
+#define LOG_TRANSPORT_STREAM            0
+#define ENABLE_RETRANSMISSION           0
 
 // Encapsulates the state of an RTP/RTCP session in the context of wifi
 // display.
@@ -39,7 +40,7 @@
             const sp<ANetworkSession> &netSession,
             const sp<AMessage> &notify,
             const struct in_addr &interfaceAddr,
-            bool legacyMode);
+            const sp<IHDCP> &hdcp);
 
     enum TransportMode {
         TRANSPORT_UDP,
@@ -84,7 +85,11 @@
         kWhatSendSR,
         kWhatRTPNotify,
         kWhatRTCPNotify,
-        kWhatSerializerNotify,
+#if ENABLE_RETRANSMISSION
+        kWhatRTPRetransmissionNotify,
+        kWhatRTCPRetransmissionNotify,
+#endif
+        kWhatMediaPullerNotify,
         kWhatConverterNotify,
         kWhatUpdateSurface,
         kWhatFinishPlay,
@@ -94,15 +99,17 @@
     static const uint32_t kSourceID = 0xdeadbeef;
     static const size_t kMaxHistoryLength = 128;
 
+#if ENABLE_RETRANSMISSION
+    static const size_t kRetransmissionPortOffset = 120;
+#endif
+
     sp<ANetworkSession> mNetSession;
     sp<AMessage> mNotify;
     in_addr mInterfaceAddr;
-    bool mLegacyMode;
+    sp<IHDCP> mHDCP;
 
     int64_t mLastLifesignUs;
 
-    sp<ALooper> mSerializerLooper;
-    sp<Serializer> mSerializer;
     sp<TSPacketizer> mPacketizer;
     sp<BufferQueue> mBufferQueue;
 
@@ -125,12 +132,20 @@
     int32_t mRTPSessionID;
     int32_t mRTCPSessionID;
 
+#if ENABLE_RETRANSMISSION
+    int32_t mRTPRetransmissionSessionID;
+    int32_t mRTCPRetransmissionSessionID;
+#endif
+
     int32_t mClientRTPPort;
     int32_t mClientRTCPPort;
     bool mRTPConnected;
     bool mRTCPConnected;
 
     uint32_t mRTPSeqNo;
+#if ENABLE_RETRANSMISSION
+    uint32_t mRTPRetransmissionSeqNo;
+#endif
 
     uint64_t mLastNTPTime;
     uint32_t mLastRTPTime;
@@ -158,8 +173,6 @@
 
     status_t setupPacketizer();
 
-    status_t addFakeSources();
-
     status_t addSource(
             bool isVideo,
             const sp<MediaSource> &source,
@@ -174,7 +187,10 @@
     void scheduleSendSR();
 
     status_t parseRTCP(const sp<ABuffer> &buffer);
+
+#if ENABLE_RETRANSMISSION
     status_t parseTSFB(const uint8_t *data, size_t size);
+#endif
 
     status_t sendPacket(int32_t sessionID, const void *data, size_t size);
     status_t onFinishPlay();
diff --git a/media/libstagefright/wifi-display/source/Serializer.cpp b/media/libstagefright/wifi-display/source/Serializer.cpp
deleted file mode 100644
index 598fd3e..0000000
--- a/media/libstagefright/wifi-display/source/Serializer.cpp
+++ /dev/null
@@ -1,370 +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 "Serializer"
-#include <utils/Log.h>
-
-#include "Serializer.h"
-
-#include <media/stagefright/foundation/ABuffer.h>
-#include <media/stagefright/foundation/ADebug.h>
-#include <media/stagefright/foundation/AMessage.h>
-#include <media/stagefright/MediaBuffer.h>
-#include <media/stagefright/MediaSource.h>
-#include <media/stagefright/MetaData.h>
-
-namespace android {
-
-struct Serializer::Track : public RefBase {
-    Track(const sp<MediaSource> &source);
-
-    status_t start();
-    status_t stop();
-
-    void readBufferIfNecessary();
-
-    bool reachedEOS() const;
-    int64_t bufferTimeUs() const;
-
-    sp<ABuffer> drainBuffer();
-
-protected:
-    virtual ~Track();
-
-private:
-    sp<MediaSource> mSource;
-    AString mMIME;
-
-    bool mStarted;
-    status_t mFinalResult;
-    MediaBuffer *mBuffer;
-    int64_t mBufferTimeUs;
-
-    DISALLOW_EVIL_CONSTRUCTORS(Track);
-};
-
-Serializer::Track::Track(const sp<MediaSource> &source)
-    : mSource(source),
-      mStarted(false),
-      mFinalResult(OK),
-      mBuffer(NULL),
-      mBufferTimeUs(-1ll) {
-    const char *mime;
-    sp<MetaData> meta = mSource->getFormat();
-    CHECK(meta->findCString(kKeyMIMEType, &mime));
-
-    mMIME = mime;
-}
-
-Serializer::Track::~Track() {
-    stop();
-}
-
-status_t Serializer::Track::start() {
-    if (mStarted) {
-        return OK;
-    }
-
-    status_t err = mSource->start();
-
-    if (err == OK) {
-        mStarted = true;
-    }
-
-    return err;
-}
-
-status_t Serializer::Track::stop() {
-    if (!mStarted) {
-        return OK;
-    }
-
-    if (mBuffer != NULL) {
-        mBuffer->release();
-        mBuffer = NULL;
-
-        mBufferTimeUs = -1ll;
-    }
-
-    status_t err = mSource->stop();
-
-    mStarted = false;
-
-    return err;
-}
-
-void Serializer::Track::readBufferIfNecessary() {
-    if (mBuffer != NULL) {
-        return;
-    }
-
-    int64_t nowUs = ALooper::GetNowUs();
-    mFinalResult = mSource->read(&mBuffer);
-    int64_t delayUs = ALooper::GetNowUs() - nowUs;
-
-    ALOGV("read on track %s took %lld us, got %d bytes",
-          mMIME.c_str(), delayUs, mBuffer->range_length());
-
-    if (mFinalResult != OK) {
-        ALOGI("read failed w/ err %d", mFinalResult);
-        return;
-    }
-
-    CHECK(mBuffer->meta_data()->findInt64(kKeyTime, &mBufferTimeUs));
-}
-
-bool Serializer::Track::reachedEOS() const {
-    return mFinalResult != OK;
-}
-
-int64_t Serializer::Track::bufferTimeUs() const {
-    return mBufferTimeUs;
-}
-
-sp<ABuffer> Serializer::Track::drainBuffer() {
-    sp<ABuffer> accessUnit = new ABuffer(mBuffer->range_length());
-
-    memcpy(accessUnit->data(),
-           (const uint8_t *)mBuffer->data() + mBuffer->range_offset(),
-           mBuffer->range_length());
-
-    accessUnit->meta()->setInt64("timeUs", mBufferTimeUs);
-    accessUnit->meta()->setPointer("mediaBuffer", mBuffer);
-
-    mBuffer = NULL;
-    mBufferTimeUs = -1ll;
-
-    return accessUnit;
-}
-
-////////////////////////////////////////////////////////////////////////////////
-
-Serializer::Serializer(bool throttle, const sp<AMessage> &notify)
-    : mThrottle(throttle),
-      mNotify(notify),
-      mPollGeneration(0),
-      mStartTimeUs(-1ll) {
-}
-
-Serializer::~Serializer() {
-}
-
-status_t Serializer::postSynchronouslyAndReturnError(
-        const sp<AMessage> &msg) {
-    sp<AMessage> response;
-    status_t err = msg->postAndAwaitResponse(&response);
-
-    if (err != OK) {
-        return err;
-    }
-
-    if (!response->findInt32("err", &err)) {
-        err = OK;
-    }
-
-    return err;
-}
-
-ssize_t Serializer::addSource(const sp<MediaSource> &source) {
-    sp<AMessage> msg = new AMessage(kWhatAddSource, id());
-    msg->setPointer("source", source.get());
-
-    sp<AMessage> response;
-    status_t err = msg->postAndAwaitResponse(&response);
-
-    if (err != OK) {
-        return err;
-    }
-
-    if (!response->findInt32("err", &err)) {
-        size_t index;
-        CHECK(response->findSize("index", &index));
-
-        return index;
-    }
-
-    return err;
-}
-
-status_t Serializer::start() {
-    return postSynchronouslyAndReturnError(new AMessage(kWhatStart, id()));
-}
-
-status_t Serializer::stop() {
-    return postSynchronouslyAndReturnError(new AMessage(kWhatStop, id()));
-}
-
-void Serializer::onMessageReceived(const sp<AMessage> &msg) {
-    switch (msg->what()) {
-        case kWhatAddSource:
-        {
-            ssize_t index = onAddSource(msg);
-
-            sp<AMessage> response = new AMessage;
-
-            if (index < 0) {
-                response->setInt32("err", index);
-            } else {
-                response->setSize("index", index);
-            }
-
-            uint32_t replyID;
-            CHECK(msg->senderAwaitsResponse(&replyID));
-
-            response->postReply(replyID);
-            break;
-        }
-
-        case kWhatStart:
-        case kWhatStop:
-        {
-            status_t err = (msg->what() == kWhatStart) ? onStart() : onStop();
-
-            sp<AMessage> response = new AMessage;
-            response->setInt32("err", err);
-
-            uint32_t replyID;
-            CHECK(msg->senderAwaitsResponse(&replyID));
-
-            response->postReply(replyID);
-            break;
-        }
-
-        case kWhatPoll:
-        {
-            int32_t generation;
-            CHECK(msg->findInt32("generation", &generation));
-
-            if (generation != mPollGeneration) {
-                break;
-            }
-
-            int64_t delayUs = onPoll();
-            if (delayUs >= 0ll) {
-                schedulePoll(delayUs);
-            }
-            break;
-        }
-
-        default:
-            TRESPASS();
-    }
-}
-
-ssize_t Serializer::onAddSource(const sp<AMessage> &msg) {
-    void *obj;
-    CHECK(msg->findPointer("source", &obj));
-
-    sp<MediaSource> source = static_cast<MediaSource *>(obj);
-
-    sp<Track> track = new Track(source);
-    return mTracks.add(track);
-}
-
-status_t Serializer::onStart() {
-    status_t err = OK;
-    for (size_t i = 0; i < mTracks.size(); ++i) {
-        err = mTracks.itemAt(i)->start();
-
-        if (err != OK) {
-            break;
-        }
-    }
-
-    if (err == OK) {
-        schedulePoll();
-    }
-
-    return err;
-}
-
-status_t Serializer::onStop() {
-    for (size_t i = 0; i < mTracks.size(); ++i) {
-        mTracks.itemAt(i)->stop();
-    }
-
-    cancelPoll();
-
-    return OK;
-}
-
-int64_t Serializer::onPoll() {
-    int64_t minTimeUs = -1ll;
-    ssize_t minTrackIndex = -1;
-
-    for (size_t i = 0; i < mTracks.size(); ++i) {
-        const sp<Track> &track = mTracks.itemAt(i);
-
-        track->readBufferIfNecessary();
-
-        if (!track->reachedEOS()) {
-            int64_t timeUs = track->bufferTimeUs();
-
-            if (minTrackIndex < 0 || timeUs < minTimeUs) {
-                minTimeUs = timeUs;
-                minTrackIndex = i;
-            }
-        }
-    }
-
-    if (minTrackIndex < 0) {
-        sp<AMessage> notify = mNotify->dup();
-        notify->setInt32("what", kWhatEOS);
-        notify->post();
-
-        return -1ll;
-    }
-
-    if (mThrottle) {
-        int64_t nowUs = ALooper::GetNowUs();
-
-        if (mStartTimeUs < 0ll) {
-            mStartTimeUs = nowUs;
-        }
-
-        int64_t lateByUs = nowUs - (minTimeUs + mStartTimeUs);
-
-        if (lateByUs < 0ll) {
-            // Too early
-            return -lateByUs;
-        }
-    }
-
-    sp<AMessage> notify = mNotify->dup();
-
-    notify->setInt32("what", kWhatAccessUnit);
-    notify->setSize("trackIndex", minTrackIndex);
-
-    notify->setBuffer(
-            "accessUnit", mTracks.itemAt(minTrackIndex)->drainBuffer());
-
-    notify->post();
-
-    return 0ll;
-}
-
-void Serializer::schedulePoll(int64_t delayUs) {
-    sp<AMessage> msg = new AMessage(kWhatPoll, id());
-    msg->setInt32("generation", mPollGeneration);
-    msg->post(delayUs);
-}
-
-void Serializer::cancelPoll() {
-    ++mPollGeneration;
-}
-
-}  // namespace android
diff --git a/media/libstagefright/wifi-display/source/Serializer.h b/media/libstagefright/wifi-display/source/Serializer.h
deleted file mode 100644
index 07950fa..0000000
--- a/media/libstagefright/wifi-display/source/Serializer.h
+++ /dev/null
@@ -1,84 +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 SERIALIZER_H_
-
-#define SERIALIZER_H_
-
-#include <media/stagefright/foundation/AHandler.h>
-#include <utils/Vector.h>
-
-namespace android {
-
-struct AMessage;
-struct MediaSource;
-
-// After adding a number of MediaSource objects and starting the Serializer,
-// it'll emit their access units in order of increasing timestamps.
-struct Serializer : public AHandler {
-    enum {
-        kWhatEOS,
-        kWhatAccessUnit
-    };
-
-    // In throttled operation, data is emitted at a pace corresponding
-    // to the incoming media timestamps.
-    Serializer(bool throttle, const sp<AMessage> &notify);
-
-    ssize_t addSource(const sp<MediaSource> &source);
-
-    status_t start();
-    status_t stop();
-
-protected:
-    virtual void onMessageReceived(const sp<AMessage> &what);
-    virtual ~Serializer();
-
-private:
-    enum {
-        kWhatAddSource,
-        kWhatStart,
-        kWhatStop,
-        kWhatPoll
-    };
-
-    struct Track;
-
-    bool mThrottle;
-    sp<AMessage> mNotify;
-    Vector<sp<Track> > mTracks;
-
-    int32_t mPollGeneration;
-
-    int64_t mStartTimeUs;
-
-    status_t postSynchronouslyAndReturnError(const sp<AMessage> &msg);
-
-    ssize_t onAddSource(const sp<AMessage> &msg);
-    status_t onStart();
-    status_t onStop();
-    int64_t onPoll();
-
-    void schedulePoll(int64_t delayUs = 0ll);
-    void cancelPoll();
-
-    DISALLOW_EVIL_CONSTRUCTORS(Serializer);
-};
-
-}  // namespace android
-
-#endif  // SERIALIZER_H_
-
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.cpp b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
index b9a3e9b..613cc28 100644
--- a/media/libstagefright/wifi-display/source/TSPacketizer.cpp
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.cpp
@@ -66,6 +66,8 @@
     AString mMIME;
     Vector<sp<ABuffer> > mCSD;
 
+    bool mAudioLacksATDSHeaders;
+
     DISALLOW_EVIL_CONSTRUCTORS(Track);
 };
 
@@ -76,7 +78,8 @@
       mPID(PID),
       mStreamType(streamType),
       mStreamID(streamID),
-      mContinuityCounter(0) {
+      mContinuityCounter(0),
+      mAudioLacksATDSHeaders(false) {
     CHECK(format->findString("mime", &mMIME));
 
     if (!strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_VIDEO_AVC)
@@ -89,6 +92,13 @@
 
             mCSD.push(csd);
         }
+
+        if (!strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
+            int32_t isADTS;
+            if (!mFormat->findInt32("is-adts", &isADTS) || isADTS == 0) {
+                mAudioLacksATDSHeaders = true;
+            }
+        }
     }
 }
 
@@ -130,16 +140,7 @@
 }
 
 bool TSPacketizer::Track::lacksADTSHeader() const {
-    if (strcasecmp(mMIME.c_str(), MEDIA_MIMETYPE_AUDIO_AAC)) {
-        return false;
-    }
-
-    int32_t isADTS;
-    if (mFormat->findInt32("is-adts", &isADTS) && isADTS != 0) {
-        return false;
-    }
-
-    return true;
+    return mAudioLacksATDSHeaders;
 }
 
 sp<ABuffer> TSPacketizer::Track::prependCSD(
@@ -278,7 +279,8 @@
         size_t trackIndex,
         const sp<ABuffer> &_accessUnit,
         sp<ABuffer> *packets,
-        uint32_t flags) {
+        uint32_t flags,
+        const uint8_t *PES_private_data, size_t PES_private_data_len) {
     sp<ABuffer> accessUnit = _accessUnit;
 
     packets->clear();
@@ -292,12 +294,13 @@
 
     const sp<Track> &track = mTracks.itemAt(trackIndex);
 
-    if (track->isH264()) {
+    if (track->isH264() && !(flags & IS_ENCRYPTED)) {
         if (IsIDR(accessUnit)) {
             // prepend codec specific data, i.e. SPS and PPS.
             accessUnit = track->prependCSD(accessUnit);
         }
     } else if (track->lacksADTSHeader()) {
+        CHECK(!(flags & IS_ENCRYPTED));
         accessUnit = track->prependADTSHeader(accessUnit);
     }
 
@@ -336,11 +339,16 @@
     // reserved = b1
     // the first fragment of "buffer" follows
 
+    size_t PES_packet_length = accessUnit->size() + 8;
+    if (PES_private_data_len > 0) {
+        PES_packet_length += PES_private_data_len + 1;
+    }
+
     size_t numTSPackets;
-    if (accessUnit->size() <= 170) {
+    if (PES_packet_length <= 178) {
         numTSPackets = 1;
     } else {
-        numTSPackets = 1 + ((accessUnit->size() - 170) + 183) / 184;
+        numTSPackets = 1 + ((PES_packet_length - 178) + 183) / 184;
     }
 
     if (flags & EMIT_PAT_AND_PMT) {
@@ -554,8 +562,7 @@
 
     uint32_t PTS = (timeUs * 9ll) / 100ll;
 
-    size_t PES_packet_length = accessUnit->size() + 8;
-    bool padding = (accessUnit->size() < (188 - 18));
+    bool padding = (PES_packet_length < (188 - 10));
 
     if (PES_packet_length >= 65536) {
         // This really should only happen for video.
@@ -572,7 +579,7 @@
     *ptr++ = (padding ? 0x30 : 0x10) | track->incrementContinuityCounter();
 
     if (padding) {
-        size_t paddingSize = 188 - 18 - accessUnit->size();
+        size_t paddingSize = 188 - 10 - PES_packet_length;
         *ptr++ = paddingSize - 1;
         if (paddingSize >= 2) {
             *ptr++ = 0x00;
@@ -588,14 +595,23 @@
     *ptr++ = PES_packet_length >> 8;
     *ptr++ = PES_packet_length & 0xff;
     *ptr++ = 0x84;
-    *ptr++ = 0x80;
-    *ptr++ = 0x05;
+    *ptr++ = (PES_private_data_len > 0) ? 0x81 : 0x80;
+
+    *ptr++ = (PES_private_data_len > 0)
+        ? (1 + PES_private_data_len + 0x05) : 0x05;
+
     *ptr++ = 0x20 | (((PTS >> 30) & 7) << 1) | 1;
     *ptr++ = (PTS >> 22) & 0xff;
     *ptr++ = (((PTS >> 15) & 0x7f) << 1) | 1;
     *ptr++ = (PTS >> 7) & 0xff;
     *ptr++ = ((PTS & 0x7f) << 1) | 1;
 
+    if (PES_private_data_len > 0) {
+        *ptr++ = 0x8e;  // PES_private_data_flag, reserved.
+        memcpy(ptr, PES_private_data, PES_private_data_len);
+        ptr += PES_private_data_len;
+    }
+
     // 18 bytes of TS/PES header leave 188 - 18 = 170 bytes for the payload
 
     size_t sizeLeft = packetDataStart + 188 - ptr;
diff --git a/media/libstagefright/wifi-display/source/TSPacketizer.h b/media/libstagefright/wifi-display/source/TSPacketizer.h
index 9dbeb27..7020fff 100644
--- a/media/libstagefright/wifi-display/source/TSPacketizer.h
+++ b/media/libstagefright/wifi-display/source/TSPacketizer.h
@@ -40,11 +40,13 @@
     enum {
         EMIT_PAT_AND_PMT = 1,
         EMIT_PCR         = 2,
+        IS_ENCRYPTED     = 4,
     };
     status_t packetize(
             size_t trackIndex, const sp<ABuffer> &accessUnit,
             sp<ABuffer> *packets,
-            uint32_t flags);
+            uint32_t flags,
+            const uint8_t *PES_private_data, size_t PES_private_data_len);
 
 protected:
     virtual ~TSPacketizer();
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index 8fead96..9ad978f 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -20,10 +20,13 @@
 
 #include "WifiDisplaySource.h"
 #include "PlaybackSession.h"
+#include "Parameters.h"
 #include "ParsedMessage.h"
 
+#include <binder/IServiceManager.h>
 #include <gui/ISurfaceTexture.h>
-
+#include <media/IHDCP.h>
+#include <media/IMediaPlayerService.h>
 #include <media/IRemoteDisplayClient.h>
 #include <media/stagefright/foundation/ABuffer.h>
 #include <media/stagefright/foundation/ADebug.h>
@@ -43,7 +46,14 @@
       mSessionID(0),
       mClientSessionID(0),
       mReaperPending(false),
-      mNextCSeq(1) {
+      mNextCSeq(1)
+#if REQUIRE_HDCP
+      ,mIsHDCP2_0(false)
+      ,mHDCPPort(0)
+      ,mHDCPInitializationComplete(false)
+      ,mSetupTriggerDeferred(false)
+#endif
+{
 }
 
 WifiDisplaySource::~WifiDisplaySource() {
@@ -203,7 +213,11 @@
 
                 case ANetworkSession::kWhatData:
                 {
-                    onReceiveClientData(msg);
+                    status_t err = onReceiveClientData(msg);
+
+                    if (err != OK) {
+                        disconnectClient(err);
+                    }
                     break;
                 }
 
@@ -220,6 +234,13 @@
 
             disconnectClient(OK);
 
+#if REQUIRE_HDCP
+            if (mHDCP != NULL) {
+                mHDCP->shutdownAsync();
+                mHDCP.clear();
+            }
+#endif
+
             status_t err = OK;
 
             sp<AMessage> response = new AMessage;
@@ -312,6 +333,40 @@
             break;
         }
 
+#if REQUIRE_HDCP
+        case kWhatHDCPNotify:
+        {
+            int32_t msgCode, ext1, ext2;
+            CHECK(msg->findInt32("msg", &msgCode));
+            CHECK(msg->findInt32("ext1", &ext1));
+            CHECK(msg->findInt32("ext2", &ext2));
+
+            ALOGV("Saw HDCP notification code %d, ext1 %d, ext2 %d",
+                    msgCode, ext1, ext2);
+
+            switch (msgCode) {
+                case HDCPModule::HDCP_INITIALIZATION_COMPLETE:
+                {
+                    mHDCPInitializationComplete = true;
+
+                    if (mSetupTriggerDeferred) {
+                        mSetupTriggerDeferred = false;
+
+                        sendM5(mClientSessionID);
+                    }
+                    break;
+                }
+
+                default:
+                {
+                    disconnectClient(-EACCES);
+                    break;
+                }
+            }
+            break;
+        }
+#endif
+
         default:
             TRESPASS();
     }
@@ -350,6 +405,9 @@
 
 status_t WifiDisplaySource::sendM3(int32_t sessionID) {
     AString body =
+#if REQUIRE_HDCP
+        "wfd_content_protection\r\n"
+#endif
         "wfd_video_formats\r\n"
         "wfd_audio_codecs\r\n"
         "wfd_client_rtp_ports\r\n";
@@ -405,9 +463,13 @@
         transportString = "TCP";
     }
 
+    // For 720p60:
+    //   use "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n"
+    // For 720p30:
+    //   use "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n"
     AString body = StringPrintf(
         "wfd_video_formats: "
-        "30 00 02 02 00000040 00000000 00000000 00 0000 0000 00 none none\r\n"
+        "28 00 02 02 00000020 00000000 00000000 00 0000 0000 00 none none\r\n"
         "wfd_audio_codecs: AAC 00000001 00\r\n"  // 2 ch AAC 48kHz
         "wfd_presentation_URL: rtsp://%s:%d/wfd1.0/streamid=0 none\r\n"
         "wfd_client_rtp_ports: RTP/AVP/%s;unicast 19000 0 mode=play\r\n",
@@ -512,6 +574,48 @@
         return ERROR_UNSUPPORTED;
     }
 
+    sp<Parameters> params =
+        Parameters::Parse(msg->getContent(), strlen(msg->getContent()));
+
+    if (params == NULL) {
+        return ERROR_MALFORMED;
+    }
+
+#if REQUIRE_HDCP
+    AString value;
+    if (!params->findParameter("wfd_content_protection", &value)) {
+        ALOGE("Sink doesn't appear to support content protection.");
+        return -EACCES;
+    }
+
+    if (value == "none") {
+        ALOGE("Sink does not support content protection.");
+        return -EACCES;
+    }
+
+    bool isHDCP2_0 = false;
+    if (value.startsWith("HDCP2.0 ")) {
+        isHDCP2_0 = true;
+    } else if (!value.startsWith("HDCP2.1 ")) {
+        return ERROR_MALFORMED;
+    }
+
+    int32_t hdcpPort;
+    if (!ParsedMessage::GetInt32Attribute(value.c_str() + 8, "port", &hdcpPort)
+            || hdcpPort < 1 || hdcpPort > 65535) {
+        return ERROR_MALFORMED;
+    }
+
+    mIsHDCP2_0 = isHDCP2_0;
+    mHDCPPort = hdcpPort;
+
+    status_t err = makeHDCP();
+    if (err != OK) {
+        ALOGE("Unable to instantiate HDCP component.");
+        return err;
+    }
+#endif
+
     return sendM4(sessionID);
 }
 
@@ -526,6 +630,15 @@
         return ERROR_UNSUPPORTED;
     }
 
+#if REQUIRE_HDCP
+    if (!mHDCPInitializationComplete) {
+        ALOGI("Deferring SETUP trigger until HDCP initialization completes.");
+
+        mSetupTriggerDeferred = true;
+        return OK;
+    }
+#endif
+
     return sendM5(sessionID);
 }
 
@@ -577,7 +690,7 @@
     msg->post(kPlaybackSessionTimeoutUs - 5000000ll);
 }
 
-void WifiDisplaySource::onReceiveClientData(const sp<AMessage> &msg) {
+status_t WifiDisplaySource::onReceiveClientData(const sp<AMessage> &msg) {
     int32_t sessionID;
     CHECK(msg->findInt32("sessionID", &sessionID));
 
@@ -597,7 +710,7 @@
     int32_t cseq;
     if (!data->findInt32("cseq", &cseq)) {
         sendErrorResponse(sessionID, "400 Bad Request", -1 /* cseq */);
-        return;
+        return ERROR_MALFORMED;
     }
 
     if (method.startsWith("RTSP/")) {
@@ -611,7 +724,7 @@
 
         if (index < 0) {
             ALOGW("Received unsolicited server response, cseq %d", cseq);
-            return;
+            return ERROR_MALFORMED;
         }
 
         HandleRTSPResponseFunc func = mResponseHandlers.valueAt(index);
@@ -623,76 +736,45 @@
             ALOGW("Response handler for session %d, cseq %d returned "
                   "err %d (%s)",
                   sessionID, cseq, err, strerror(-err));
-        }
-    } else {
-        AString version;
-        data->getRequestField(2, &version);
-        if (!(version == AString("RTSP/1.0"))) {
-            sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
-            return;
+
+            return err;
         }
 
-        if (method == "DESCRIBE") {
-            onDescribeRequest(sessionID, cseq, data);
-        } else if (method == "OPTIONS") {
-            onOptionsRequest(sessionID, cseq, data);
-        } else if (method == "SETUP") {
-            onSetupRequest(sessionID, cseq, data);
-        } else if (method == "PLAY") {
-            onPlayRequest(sessionID, cseq, data);
-        } else if (method == "PAUSE") {
-            onPauseRequest(sessionID, cseq, data);
-        } else if (method == "TEARDOWN") {
-            onTeardownRequest(sessionID, cseq, data);
-        } else if (method == "GET_PARAMETER") {
-            onGetParameterRequest(sessionID, cseq, data);
-        } else if (method == "SET_PARAMETER") {
-            onSetParameterRequest(sessionID, cseq, data);
-        } else {
-            sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
-        }
+        return OK;
     }
+
+    AString version;
+    data->getRequestField(2, &version);
+    if (!(version == AString("RTSP/1.0"))) {
+        sendErrorResponse(sessionID, "505 RTSP Version not supported", cseq);
+        return ERROR_UNSUPPORTED;
+    }
+
+    status_t err;
+    if (method == "OPTIONS") {
+        err = onOptionsRequest(sessionID, cseq, data);
+    } else if (method == "SETUP") {
+        err = onSetupRequest(sessionID, cseq, data);
+    } else if (method == "PLAY") {
+        err = onPlayRequest(sessionID, cseq, data);
+    } else if (method == "PAUSE") {
+        err = onPauseRequest(sessionID, cseq, data);
+    } else if (method == "TEARDOWN") {
+        err = onTeardownRequest(sessionID, cseq, data);
+    } else if (method == "GET_PARAMETER") {
+        err = onGetParameterRequest(sessionID, cseq, data);
+    } else if (method == "SET_PARAMETER") {
+        err = onSetParameterRequest(sessionID, cseq, data);
+    } else {
+        sendErrorResponse(sessionID, "405 Method Not Allowed", cseq);
+
+        err = ERROR_UNSUPPORTED;
+    }
+
+    return err;
 }
 
-void WifiDisplaySource::onDescribeRequest(
-        int32_t sessionID,
-        int32_t cseq,
-        const sp<ParsedMessage> &data) {
-    int64_t nowUs = ALooper::GetNowUs();
-
-    AString sdp;
-    sdp.append("v=0\r\n");
-
-    sdp.append(StringPrintf(
-                "o=- %lld %lld IN IP4 0.0.0.0\r\n", nowUs, nowUs));
-
-    sdp.append(
-            "o=- 0 0 IN IP4 127.0.0.0\r\n"
-            "s=Sample\r\n"
-            "c=IN IP4 0.0.0.0\r\n"
-            "b=AS:502\r\n"
-            "t=0 0\r\n"
-            "a=control:*\r\n"
-            "a=range:npt=now-\r\n"
-            "m=video 0 RTP/AVP 33\r\n"
-            "a=rtpmap:33 MP2T/90000\r\n"
-            "a=control:\r\n");
-
-    AString response = "RTSP/1.0 200 OK\r\n";
-    AppendCommonResponse(&response, cseq);
-
-    response.append("Content-Type: application/sdp\r\n");
-
-    // response.append("Content-Base: rtsp://0.0.0.0:7236\r\n");
-    response.append(StringPrintf("Content-Length: %d\r\n", sdp.size()));
-    response.append("\r\n");
-    response.append(sdp);
-
-    status_t err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
-}
-
-void WifiDisplaySource::onOptionsRequest(
+status_t WifiDisplaySource::onOptionsRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -708,19 +790,21 @@
     AppendCommonResponse(&response, cseq);
 
     response.append(
-            "Public: org.wfa.wfd1.0, DESCRIBE, SETUP, TEARDOWN, PLAY, PAUSE, "
+            "Public: org.wfa.wfd1.0, SETUP, TEARDOWN, PLAY, PAUSE, "
             "GET_PARAMETER, SET_PARAMETER\r\n");
 
     response.append("\r\n");
 
     status_t err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
 
-    err = sendM3(sessionID);
-    CHECK_EQ(err, (status_t)OK);
+    if (err == OK) {
+        err = sendM3(sessionID);
+    }
+
+    return err;
 }
 
-void WifiDisplaySource::onSetupRequest(
+status_t WifiDisplaySource::onSetupRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -729,13 +813,13 @@
         // We only support a single playback session per client.
         // This is due to the reversed keep-alive design in the wfd specs...
         sendErrorResponse(sessionID, "400 Bad Request", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     AString transport;
     if (!data->findString("transport", &transport)) {
         sendErrorResponse(sessionID, "400 Bad Request", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     PlaybackSession::TransportMode transportMode =
@@ -767,7 +851,7 @@
 
             if (badRequest) {
                 sendErrorResponse(sessionID, "400 Bad Request", cseq);
-                return;
+                return ERROR_MALFORMED;
             }
 
             transportMode = PlaybackSession::TRANSPORT_TCP;
@@ -785,13 +869,14 @@
         } else if (sscanf(clientPort.c_str(), "%d", &clientRtp) == 1) {
             // No RTCP.
             clientRtcp = -1;
+            clientRtcp = clientRtp + 1;  // XXX
         } else {
             badRequest = true;
         }
 
         if (badRequest) {
             sendErrorResponse(sessionID, "400 Bad Request", cseq);
-            return;
+            return ERROR_MALFORMED;
         }
 #if 1
     // The older LG dongles doesn't specify client_port=xxx apparently.
@@ -801,7 +886,7 @@
 #endif
     } else {
         sendErrorResponse(sessionID, "461 Unsupported Transport", cseq);
-        return;
+        return ERROR_UNSUPPORTED;
     }
 
     int32_t playbackSessionID = makeUniquePlaybackSessionID();
@@ -813,7 +898,12 @@
     sp<PlaybackSession> playbackSession =
         new PlaybackSession(
                 mNetSession, notify, mInterfaceAddr,
-                mClient == NULL /* legacyMode */);
+#if REQUIRE_HDCP
+                mHDCP
+#else
+                NULL
+#endif
+                );
 
     looper()->registerHandler(playbackSession);
 
@@ -822,12 +912,12 @@
 
     if (strncasecmp("rtsp://", uri.c_str(), 7)) {
         sendErrorResponse(sessionID, "400 Bad Request", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     if (!(uri.startsWith("rtsp://") && uri.endsWith("/wfd1.0/streamid=0"))) {
         sendErrorResponse(sessionID, "404 Not found", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     status_t err = playbackSession->init(
@@ -846,10 +936,10 @@
             break;
         case -ENOENT:
             sendErrorResponse(sessionID, "404 Not Found", cseq);
-            return;
+            return err;
         default:
             sendErrorResponse(sessionID, "403 Forbidden", cseq);
-            return;
+            return err;
     }
 
     mClientInfo.mPlaybackSessionID = playbackSessionID;
@@ -891,13 +981,18 @@
     response.append("\r\n");
 
     err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
+
+    if (err != OK) {
+        return err;
+    }
 
     scheduleReaper();
     scheduleKeepAlive(sessionID);
+
+    return OK;
 }
 
-void WifiDisplaySource::onPlayRequest(
+status_t WifiDisplaySource::onPlayRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -907,7 +1002,7 @@
 
     if (playbackSession == NULL) {
         sendErrorResponse(sessionID, "454 Session Not Found", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     status_t err = playbackSession->play();
@@ -919,12 +1014,17 @@
     response.append("\r\n");
 
     err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
+
+    if (err != OK) {
+        return err;
+    }
 
     playbackSession->finishPlay();
+
+    return OK;
 }
 
-void WifiDisplaySource::onPauseRequest(
+status_t WifiDisplaySource::onPauseRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -934,7 +1034,7 @@
 
     if (playbackSession == NULL) {
         sendErrorResponse(sessionID, "454 Session Not Found", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     status_t err = playbackSession->pause();
@@ -945,10 +1045,11 @@
     response.append("\r\n");
 
     err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
+
+    return err;
 }
 
-void WifiDisplaySource::onTeardownRequest(
+status_t WifiDisplaySource::onTeardownRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -958,7 +1059,7 @@
 
     if (playbackSession == NULL) {
         sendErrorResponse(sessionID, "454 Session Not Found", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     AString response = "RTSP/1.0 200 OK\r\n";
@@ -967,12 +1068,17 @@
     response.append("\r\n");
 
     status_t err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
+
+    if (err != OK) {
+        return err;
+    }
 
     disconnectClient(UNKNOWN_ERROR);
+
+    return OK;
 }
 
-void WifiDisplaySource::onGetParameterRequest(
+status_t WifiDisplaySource::onGetParameterRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -982,7 +1088,7 @@
 
     if (playbackSession == NULL) {
         sendErrorResponse(sessionID, "454 Session Not Found", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
     playbackSession->updateLiveness();
@@ -992,10 +1098,10 @@
     response.append("\r\n");
 
     status_t err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
+    return err;
 }
 
-void WifiDisplaySource::onSetParameterRequest(
+status_t WifiDisplaySource::onSetParameterRequest(
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
@@ -1005,11 +1111,12 @@
 
     if (playbackSession == NULL) {
         sendErrorResponse(sessionID, "454 Session Not Found", cseq);
-        return;
+        return ERROR_MALFORMED;
     }
 
-    // XXX check that the parameter is about that.
-    playbackSession->requestIDRFrame();
+    if (strstr(data->getContent(), "wfd_idr_request\r\n")) {
+        playbackSession->requestIDRFrame();
+    }
 
     playbackSession->updateLiveness();
 
@@ -1018,7 +1125,7 @@
     response.append("\r\n");
 
     status_t err = mNetSession->sendRequest(sessionID, response.c_str());
-    CHECK_EQ(err, (status_t)OK);
+    return err;
 }
 
 // static
@@ -1103,5 +1210,71 @@
     }
 }
 
+#if REQUIRE_HDCP
+struct WifiDisplaySource::HDCPObserver : public BnHDCPObserver {
+    HDCPObserver(const sp<AMessage> &notify);
+
+    virtual void notify(
+            int msg, int ext1, int ext2, const Parcel *obj);
+
+private:
+    sp<AMessage> mNotify;
+
+    DISALLOW_EVIL_CONSTRUCTORS(HDCPObserver);
+};
+
+WifiDisplaySource::HDCPObserver::HDCPObserver(
+        const sp<AMessage> &notify)
+    : mNotify(notify) {
+}
+
+void WifiDisplaySource::HDCPObserver::notify(
+        int msg, int ext1, int ext2, const Parcel *obj) {
+    sp<AMessage> notify = mNotify->dup();
+    notify->setInt32("msg", msg);
+    notify->setInt32("ext1", ext1);
+    notify->setInt32("ext2", ext2);
+    notify->post();
+}
+
+status_t WifiDisplaySource::makeHDCP() {
+    sp<IServiceManager> sm = defaultServiceManager();
+    sp<IBinder> binder = sm->getService(String16("media.player"));
+    sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
+    CHECK(service != NULL);
+
+    mHDCP = service->makeHDCP();
+
+    if (mHDCP == NULL) {
+        return ERROR_UNSUPPORTED;
+    }
+
+    sp<AMessage> notify = new AMessage(kWhatHDCPNotify, id());
+    mHDCPObserver = new HDCPObserver(notify);
+
+    status_t err = mHDCP->setObserver(mHDCPObserver);
+
+    if (err != OK) {
+        ALOGE("Failed to set HDCP observer.");
+
+        mHDCPObserver.clear();
+        mHDCP.clear();
+
+        return err;
+    }
+
+    ALOGI("initiating HDCP negotiation w/ host %s:%d",
+            mClientInfo.mRemoteIP.c_str(), mHDCPPort);
+
+    err = mHDCP->initAsync(mClientInfo.mRemoteIP.c_str(), mHDCPPort);
+
+    if (err != OK) {
+        return err;
+    }
+
+    return OK;
+}
+#endif
+
 }  // namespace android
 
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index 3c8d50f..298cb9b 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -26,6 +26,9 @@
 
 namespace android {
 
+#define REQUIRE_HDCP    0
+
+struct IHDCP;
 struct IRemoteDisplayClient;
 struct ParsedMessage;
 
@@ -48,6 +51,10 @@
 private:
     struct PlaybackSession;
 
+#if REQUIRE_HDCP
+    struct HDCPObserver;
+#endif
+
     enum {
         kWhatStart,
         kWhatRTSPNotify,
@@ -55,6 +62,7 @@
         kWhatReapDeadClients,
         kWhatPlaybackSessionNotify,
         kWhatKeepAlive,
+        kWhatHDCPNotify,
     };
 
     struct ResponseID {
@@ -100,6 +108,18 @@
 
     KeyedVector<ResponseID, HandleRTSPResponseFunc> mResponseHandlers;
 
+#if REQUIRE_HDCP
+    bool mIsHDCP2_0;
+    int32_t mHDCPPort;
+    sp<IHDCP> mHDCP;
+    sp<HDCPObserver> mHDCPObserver;
+
+    bool mHDCPInitializationComplete;
+    bool mSetupTriggerDeferred;
+
+    status_t makeHDCP();
+#endif
+
     status_t sendM1(int32_t sessionID);
     status_t sendM3(int32_t sessionID);
     status_t sendM4(int32_t sessionID);
@@ -124,44 +144,39 @@
     void registerResponseHandler(
             int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
 
-    void onReceiveClientData(const sp<AMessage> &msg);
+    status_t onReceiveClientData(const sp<AMessage> &msg);
 
-    void onDescribeRequest(
+    status_t onOptionsRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
 
-    void onOptionsRequest(
+    status_t onSetupRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
 
-    void onSetupRequest(
+    status_t onPlayRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
 
-    void onPlayRequest(
+    status_t onPauseRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
 
-    void onPauseRequest(
+    status_t onTeardownRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
 
-    void onTeardownRequest(
+    status_t onGetParameterRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
 
-    void onGetParameterRequest(
-            int32_t sessionID,
-            int32_t cseq,
-            const sp<ParsedMessage> &data);
-
-    void onSetParameterRequest(
+    status_t onSetParameterRequest(
             int32_t sessionID,
             int32_t cseq,
             const sp<ParsedMessage> &data);
diff --git a/media/libstagefright/wifi-display/wfd.cpp b/media/libstagefright/wifi-display/wfd.cpp
index d886f14..011edab 100644
--- a/media/libstagefright/wifi-display/wfd.cpp
+++ b/media/libstagefright/wifi-display/wfd.cpp
@@ -29,27 +29,13 @@
 
 namespace android {
 
-static void enableDisableRemoteDisplay(const char *iface) {
-    sp<IServiceManager> sm = defaultServiceManager();
-    sp<IBinder> binder = sm->getService(String16("media.player"));
-
-    sp<IMediaPlayerService> service =
-        interface_cast<IMediaPlayerService>(binder);
-
-    CHECK(service.get() != NULL);
-
-    service->enableRemoteDisplay(iface);
-}
-
 }  // namespace android
 
 static void usage(const char *me) {
     fprintf(stderr,
             "usage:\n"
             "           %s -c host[:port]\tconnect to wifi source\n"
-            "           -u uri        \tconnect to an rtsp uri\n"
-            "           -e ip[:port]       \tenable remote display\n"
-            "           -d            \tdisable remote display\n",
+            "           -u uri        \tconnect to an rtsp uri\n",
             me);
 }
 
@@ -65,7 +51,7 @@
     AString uri;
 
     int res;
-    while ((res = getopt(argc, argv, "hc:l:u:e:d")) >= 0) {
+    while ((res = getopt(argc, argv, "hc:l:u:")) >= 0) {
         switch (res) {
             case 'c':
             {
@@ -95,20 +81,6 @@
                 break;
             }
 
-            case 'e':
-            {
-                enableDisableRemoteDisplay(optarg);
-                exit(0);
-                break;
-            }
-
-            case 'd':
-            {
-                enableDisableRemoteDisplay(NULL);
-                exit(0);
-                break;
-            }
-
             case '?':
             case 'h':
             default:
diff --git a/services/camera/libcameraservice/Camera2Client.cpp b/services/camera/libcameraservice/Camera2Client.cpp
index 4fad83e..495feda 100644
--- a/services/camera/libcameraservice/Camera2Client.cpp
+++ b/services/camera/libcameraservice/Camera2Client.cpp
@@ -137,7 +137,6 @@
 
 Camera2Client::~Camera2Client() {
     ATRACE_CALL();
-    ALOGV("Camera %d: Shutting down", mCameraId);
 
     mDestructionStarted = true;
 
@@ -145,12 +144,6 @@
     mClientPid = getCallingPid();
     disconnect();
 
-    mFrameProcessor->requestExit();
-    mCaptureSequencer->requestExit();
-    mJpegProcessor->requestExit();
-    mZslProcessor->requestExit();
-    mCallbackProcessor->requestExit();
-
     ALOGI("Camera %d: Closed", mCameraId);
 }
 
@@ -365,15 +358,21 @@
 
 void Camera2Client::disconnect() {
     ATRACE_CALL();
-    ALOGV("%s: E", __FUNCTION__);
     Mutex::Autolock icl(mICameraLock);
     status_t res;
     if ( (res = checkPid(__FUNCTION__) ) != OK) return;
 
     if (mDevice == 0) return;
 
+    ALOGV("Camera %d: Shutting down", mCameraId);
+
     stopPreviewL();
 
+    {
+        SharedParameters::Lock l(mParameters);
+        l.mParameters.state = Parameters::DISCONNECTED;
+    }
+
     if (mPreviewStreamId != NO_STREAM) {
         mDevice->deleteStream(mPreviewStreamId);
         mPreviewStreamId = NO_STREAM;
@@ -390,9 +389,25 @@
 
     mZslProcessor->deleteStream();
 
+    mFrameProcessor->requestExit();
+    mCaptureSequencer->requestExit();
+    mJpegProcessor->requestExit();
+    mZslProcessor->requestExit();
+    mCallbackProcessor->requestExit();
+
+    ALOGV("Camera %d: Waiting for threads", mCameraId);
+
+    mFrameProcessor->join();
+    mCaptureSequencer->join();
+    mJpegProcessor->join();
+    mZslProcessor->join();
+    mCallbackProcessor->join();
+
+    ALOGV("Camera %d: Disconnecting device", mCameraId);
+
+    mDevice->disconnect();
+
     mDevice.clear();
-    SharedParameters::Lock l(mParameters);
-    l.mParameters.state = Parameters::DISCONNECTED;
 
     CameraService::Client::disconnect();
 }
diff --git a/services/camera/libcameraservice/Camera2Device.cpp b/services/camera/libcameraservice/Camera2Device.cpp
index a171c46..81c0496 100644
--- a/services/camera/libcameraservice/Camera2Device.cpp
+++ b/services/camera/libcameraservice/Camera2Device.cpp
@@ -38,30 +38,25 @@
 
 Camera2Device::~Camera2Device()
 {
-    ALOGV("%s: Shutting down device for camera %d", __FUNCTION__, mId);
-    if (mDevice) {
-        status_t res;
-        res = mDevice->common.close(&mDevice->common);
-        if (res != OK) {
-            ALOGE("%s: Could not close camera %d: %s (%d)",
-                    __FUNCTION__,
-                    mId, strerror(-res), res);
-        }
-        mDevice = NULL;
-    }
-    ALOGV("%s: Shutdown complete", __FUNCTION__);
+    disconnect();
 }
 
 status_t Camera2Device::initialize(camera_module_t *module)
 {
     ALOGV("%s: Initializing device for camera %d", __FUNCTION__, mId);
+    if (mDevice != NULL) {
+        ALOGE("%s: Already initialized!", __FUNCTION__);
+        return INVALID_OPERATION;
+    }
 
     status_t res;
     char name[10];
     snprintf(name, sizeof(name), "%d", mId);
 
+    camera2_device_t *device;
+
     res = module->common.methods->open(&module->common, name,
-            reinterpret_cast<hw_device_t**>(&mDevice));
+            reinterpret_cast<hw_device_t**>(&device));
 
     if (res != OK) {
         ALOGE("%s: Could not open camera %d: %s (%d)", __FUNCTION__,
@@ -69,11 +64,12 @@
         return res;
     }
 
-    if (mDevice->common.version != CAMERA_DEVICE_API_VERSION_2_0) {
+    if (device->common.version != CAMERA_DEVICE_API_VERSION_2_0) {
         ALOGE("%s: Could not open camera %d: "
                 "Camera device is not version %x, reports %x instead",
                 __FUNCTION__, mId, CAMERA_DEVICE_API_VERSION_2_0,
-                mDevice->common.version);
+                device->common.version);
+        device->common.close(&device->common);
         return BAD_VALUE;
     }
 
@@ -81,45 +77,81 @@
     res = module->get_camera_info(mId, &info);
     if (res != OK ) return res;
 
-    if (info.device_version != mDevice->common.version) {
+    if (info.device_version != device->common.version) {
         ALOGE("%s: HAL reporting mismatched camera_info version (%x)"
                 " and device version (%x).", __FUNCTION__,
-                mDevice->common.version, info.device_version);
+                device->common.version, info.device_version);
+        device->common.close(&device->common);
         return BAD_VALUE;
     }
 
-    mDeviceInfo = info.static_camera_characteristics;
-
-    res = mRequestQueue.setConsumerDevice(mDevice);
+    res = mRequestQueue.setConsumerDevice(device);
     if (res != OK) {
         ALOGE("%s: Camera %d: Unable to connect request queue to device: %s (%d)",
                 __FUNCTION__, mId, strerror(-res), res);
+        device->common.close(&device->common);
         return res;
     }
-    res = mFrameQueue.setProducerDevice(mDevice);
+    res = mFrameQueue.setProducerDevice(device);
     if (res != OK) {
         ALOGE("%s: Camera %d: Unable to connect frame queue to device: %s (%d)",
                 __FUNCTION__, mId, strerror(-res), res);
+        device->common.close(&device->common);
         return res;
     }
 
-    res = mDevice->ops->get_metadata_vendor_tag_ops(mDevice, &mVendorTagOps);
+    res = device->ops->get_metadata_vendor_tag_ops(device, &mVendorTagOps);
     if (res != OK ) {
         ALOGE("%s: Camera %d: Unable to retrieve tag ops from device: %s (%d)",
                 __FUNCTION__, mId, strerror(-res), res);
+        device->common.close(&device->common);
         return res;
     }
     res = set_camera_metadata_vendor_tag_ops(mVendorTagOps);
     if (res != OK) {
         ALOGE("%s: Camera %d: Unable to set tag ops: %s (%d)",
             __FUNCTION__, mId, strerror(-res), res);
+        device->common.close(&device->common);
         return res;
     }
-    setNotifyCallback(NULL);
+    res = device->ops->set_notify_callback(device, notificationCallback,
+            NULL);
+    if (res != OK) {
+        ALOGE("%s: Camera %d: Unable to initialize notification callback!",
+                __FUNCTION__, mId);
+        device->common.close(&device->common);
+        return res;
+    }
+
+    mDeviceInfo = info.static_camera_characteristics;
+    mDevice = device;
 
     return OK;
 }
 
+status_t Camera2Device::disconnect() {
+    status_t res = OK;
+    if (mDevice) {
+        ALOGV("%s: Closing device for camera %d", __FUNCTION__, mId);
+
+        int inProgressCount = mDevice->ops->get_in_progress_count(mDevice);
+        if (inProgressCount > 0) {
+            ALOGW("%s: Closing camera device %d with %d requests in flight!",
+                    __FUNCTION__, mId, inProgressCount);
+        }
+        mStreams.clear();
+        res = mDevice->common.close(&mDevice->common);
+        if (res != OK) {
+            ALOGE("%s: Could not close camera %d: %s (%d)",
+                    __FUNCTION__,
+                    mId, strerror(-res), res);
+        }
+        mDevice = NULL;
+        ALOGV("%s: Shutdown complete", __FUNCTION__);
+    }
+    return res;
+}
+
 status_t Camera2Device::dump(int fd, const Vector<String16>& args) {
 
     String8 result;
@@ -354,7 +386,7 @@
 status_t Camera2Device::waitUntilDrained() {
     static const uint32_t kSleepTime = 50000; // 50 ms
     static const uint32_t kMaxSleepTime = 10000000; // 10 s
-    ALOGV("%s: E", __FUNCTION__);
+    ALOGV("%s: Camera %d: Starting wait", __FUNCTION__, mId);
     if (mRequestQueue.getBufferCount() ==
             CAMERA2_REQUEST_QUEUE_IS_BOTTOMLESS) return INVALID_OPERATION;
 
@@ -364,11 +396,12 @@
         usleep(kSleepTime);
         totalTime += kSleepTime;
         if (totalTime > kMaxSleepTime) {
-            ALOGE("%s: Waited %d us, requests still in flight", __FUNCTION__,
-                    totalTime);
+            ALOGE("%s: Waited %d us, %d requests still in flight", __FUNCTION__,
+                    mDevice->ops->get_in_progress_count(mDevice), totalTime);
             return TIMED_OUT;
         }
     }
+    ALOGV("%s: Camera %d: HAL is idle", __FUNCTION__, mId);
     return OK;
 }
 
diff --git a/services/camera/libcameraservice/Camera2Device.h b/services/camera/libcameraservice/Camera2Device.h
index a327d8d..38662e3 100644
--- a/services/camera/libcameraservice/Camera2Device.h
+++ b/services/camera/libcameraservice/Camera2Device.h
@@ -40,6 +40,7 @@
     ~Camera2Device();
 
     status_t initialize(camera_module_t *module);
+    status_t disconnect();
 
     status_t dump(int fd, const Vector<String16>& args);
 
@@ -191,7 +192,6 @@
             buffer_handle_t *buffer, wp<BufferReleasedListener> listener);
 
   private:
-
     const int mId;
     camera2_device_t *mDevice;
 
diff --git a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
index eb9eb5b..bdd8fa9 100644
--- a/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
+++ b/services/camera/libcameraservice/camera2/CaptureSequencer.cpp
@@ -96,11 +96,13 @@
     }
 }
 
-void CaptureSequencer::onCaptureAvailable(nsecs_t timestamp) {
+void CaptureSequencer::onCaptureAvailable(nsecs_t timestamp,
+        sp<MemoryBase> captureBuffer) {
     ATRACE_CALL();
     ALOGV("%s", __FUNCTION__);
     Mutex::Autolock l(mInputMutex);
     mCaptureTimestamp = timestamp;
+    mCaptureBuffer = captureBuffer;
     if (!mNewCaptureReceived) {
         mNewCaptureReceived = true;
         mNewCaptureSignal.signal();
@@ -195,7 +197,7 @@
 }
 
 CaptureSequencer::CaptureState CaptureSequencer::manageDone(sp<Camera2Client> &client) {
-    status_t res;
+    status_t res = OK;
     ATRACE_CALL();
     mCaptureId++;
 
@@ -204,20 +206,39 @@
         mBusy = false;
     }
 
-    SharedParameters::Lock l(client->getParameters());
-    switch (l.mParameters.state) {
-        case Parameters::STILL_CAPTURE:
-            l.mParameters.state = Parameters::STOPPED;
-            break;
-        case Parameters::VIDEO_SNAPSHOT:
-            l.mParameters.state = Parameters::RECORD;
-            break;
-        default:
-            ALOGE("%s: Camera %d: Still image produced unexpectedly "
-                    "in state %s!",
-                    __FUNCTION__, client->getCameraId(),
-                    Parameters::getStateName(l.mParameters.state));
+    {
+        SharedParameters::Lock l(client->getParameters());
+        switch (l.mParameters.state) {
+            case Parameters::STILL_CAPTURE:
+                l.mParameters.state = Parameters::STOPPED;
+                break;
+            case Parameters::VIDEO_SNAPSHOT:
+                l.mParameters.state = Parameters::RECORD;
+                break;
+            default:
+                ALOGE("%s: Camera %d: Still image produced unexpectedly "
+                        "in state %s!",
+                        __FUNCTION__, client->getCameraId(),
+                        Parameters::getStateName(l.mParameters.state));
+                res = INVALID_OPERATION;
+        }
     }
+    sp<ZslProcessor> processor = mZslProcessor.promote();
+    if (processor != 0) {
+        processor->clearZslQueue();
+    }
+
+    if (mCaptureBuffer != 0 && res == OK) {
+        Camera2Client::SharedCameraClient::Lock l(client->mSharedCameraClient);
+        ALOGV("%s: Sending still image to client", __FUNCTION__);
+        if (l.mCameraClient != 0) {
+            l.mCameraClient->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE,
+                    mCaptureBuffer, NULL);
+        } else {
+            ALOGV("%s: No client!", __FUNCTION__);
+        }
+    }
+    mCaptureBuffer.clear();
 
     return IDLE;
 }
@@ -242,7 +263,8 @@
         nextState = BURST_CAPTURE_START;
     }
     else if (l.mParameters.zslMode &&
-            l.mParameters.state == Parameters::STILL_CAPTURE) {
+            l.mParameters.state == Parameters::STILL_CAPTURE &&
+            l.mParameters.flashMode != Parameters::FLASH_MODE_ON) {
         nextState = ZSL_START;
     } else {
         nextState = STANDARD_START;
@@ -285,6 +307,11 @@
         return STANDARD_START;
     }
 
+    SharedParameters::Lock l(client->getParameters());
+    if (l.mParameters.playShutterSound) {
+        client->getCameraService()->playSound(CameraService::SOUND_SHUTTER);
+    }
+
     mTimeoutCount = kMaxTimeoutsForCaptureEnd;
     return STANDARD_CAPTURE_WAIT;
 }
diff --git a/services/camera/libcameraservice/camera2/CaptureSequencer.h b/services/camera/libcameraservice/camera2/CaptureSequencer.h
index c5c5654..f0d1e79 100644
--- a/services/camera/libcameraservice/camera2/CaptureSequencer.h
+++ b/services/camera/libcameraservice/camera2/CaptureSequencer.h
@@ -17,6 +17,7 @@
 #ifndef ANDROID_SERVERS_CAMERA_CAMERA2_CAPTURESEQUENCER_H
 #define ANDROID_SERVERS_CAMERA_CAMERA2_CAPTURESEQUENCER_H
 
+#include <binder/MemoryBase.h>
 #include <utils/Thread.h>
 #include <utils/String16.h>
 #include <utils/Vector.h>
@@ -58,8 +59,8 @@
     // Notifications from the frame processor
     virtual void onFrameAvailable(int32_t frameId, CameraMetadata &frame);
 
-    // Notifications from the capture processor
-    void onCaptureAvailable(nsecs_t timestamp);
+    // Notifications from the JPEG processor
+    void onCaptureAvailable(nsecs_t timestamp, sp<MemoryBase> captureBuffer);
 
     void dump(int fd, const Vector<String16>& args);
 
@@ -85,6 +86,7 @@
 
     bool mNewCaptureReceived;
     nsecs_t mCaptureTimestamp;
+    sp<MemoryBase> mCaptureBuffer;
     Condition mNewCaptureSignal;
 
     /**
@@ -92,7 +94,7 @@
      */
     static const nsecs_t kWaitDuration = 100000000; // 100 ms
     static const int kMaxTimeoutsForPrecaptureStart = 2; // 200 ms
-    static const int kMaxTimeoutsForPrecaptureEnd = 10;  // 1 sec
+    static const int kMaxTimeoutsForPrecaptureEnd = 20;  // 2 sec
     static const int kMaxTimeoutsForCaptureEnd    = 40;  // 4 sec
 
     wp<Camera2Client> mClient;
diff --git a/services/camera/libcameraservice/camera2/FrameProcessor.cpp b/services/camera/libcameraservice/camera2/FrameProcessor.cpp
index a7d19aa..d3d7b3e 100644
--- a/services/camera/libcameraservice/camera2/FrameProcessor.cpp
+++ b/services/camera/libcameraservice/camera2/FrameProcessor.cpp
@@ -29,7 +29,7 @@
 namespace camera2 {
 
 FrameProcessor::FrameProcessor(wp<Camera2Client> client):
-        Thread(false), mClient(client) {
+        Thread(false), mClient(client), mLastFrameNumberOfFaces(0) {
 }
 
 FrameProcessor::~FrameProcessor() {
@@ -148,7 +148,7 @@
 
 status_t FrameProcessor::processFaceDetect(const CameraMetadata &frame,
         sp<Camera2Client> &client) {
-    status_t res;
+    status_t res = BAD_VALUE;
     ATRACE_CALL();
     camera_metadata_ro_entry_t entry;
     bool enableFaceDetect;
@@ -175,7 +175,9 @@
         entry = frame.find(ANDROID_STATS_FACE_RECTANGLES);
         if (entry.count == 0) {
             // No faces this frame
-            return res;
+            /* warning: locks SharedCameraClient */
+            callbackFaceDetection(client, metadata);
+            return OK;
         }
         metadata.number_of_faces = entry.count / 4;
         if (metadata.number_of_faces >
@@ -260,14 +262,25 @@
         metadata.faces = faces.editArray();
     }
 
-    if (metadata.number_of_faces != 0) {
+    /* warning: locks SharedCameraClient */
+    callbackFaceDetection(client, metadata);
+
+    return OK;
+}
+
+void FrameProcessor::callbackFaceDetection(sp<Camera2Client> client,
+                               /*in*/camera_frame_metadata &metadata) {
+
+    /* Filter out repeated 0-face callbacks, but not when the last frame was >0 */
+    if (metadata.number_of_faces != 0 || mLastFrameNumberOfFaces != metadata.number_of_faces) {
         Camera2Client::SharedCameraClient::Lock l(client->mSharedCameraClient);
         if (l.mCameraClient != NULL) {
             l.mCameraClient->dataCallback(CAMERA_MSG_PREVIEW_METADATA,
                     NULL, &metadata);
         }
     }
-    return OK;
+
+    mLastFrameNumberOfFaces = metadata.number_of_faces;
 }
 
 }; // namespace camera2
diff --git a/services/camera/libcameraservice/camera2/FrameProcessor.h b/services/camera/libcameraservice/camera2/FrameProcessor.h
index 25d489a..cc8c128 100644
--- a/services/camera/libcameraservice/camera2/FrameProcessor.h
+++ b/services/camera/libcameraservice/camera2/FrameProcessor.h
@@ -23,6 +23,8 @@
 #include <utils/KeyedVector.h>
 #include "CameraMetadata.h"
 
+struct camera_frame_metadata;
+
 namespace android {
 
 class Camera2Client;
@@ -67,6 +69,11 @@
             sp<Camera2Client> &client);
 
     CameraMetadata mLastFrame;
+    int mLastFrameNumberOfFaces;
+
+    // Emit FaceDetection event to java if faces changed
+    void callbackFaceDetection(sp<Camera2Client> client,
+                               camera_frame_metadata &metadata);
 };
 
 
diff --git a/services/camera/libcameraservice/camera2/JpegProcessor.cpp b/services/camera/libcameraservice/camera2/JpegProcessor.cpp
index b230d2d..7ab9c87 100644
--- a/services/camera/libcameraservice/camera2/JpegProcessor.cpp
+++ b/services/camera/libcameraservice/camera2/JpegProcessor.cpp
@@ -230,11 +230,6 @@
         return OK;
     }
 
-    sp<CaptureSequencer> sequencer = mSequencer.promote();
-    if (sequencer != 0) {
-        sequencer->onCaptureAvailable(imgBuffer.timestamp);
-    }
-
     // TODO: Optimize this to avoid memcopy
     void* captureMemory = mCaptureHeap->mHeap->getBase();
     size_t size = mCaptureHeap->mHeap->getSize();
@@ -242,16 +237,11 @@
 
     mCaptureConsumer->unlockBuffer(imgBuffer);
 
-    captureHeap = mCaptureHeap;
-
-    Camera2Client::SharedCameraClient::Lock l(client->mSharedCameraClient);
-    ALOGV("%s: Sending still image to client", __FUNCTION__);
-    if (l.mCameraClient != 0) {
-        l.mCameraClient->dataCallback(CAMERA_MSG_COMPRESSED_IMAGE,
-                captureHeap->mBuffers[0], NULL);
-    } else {
-        ALOGV("%s: No client!", __FUNCTION__);
+    sp<CaptureSequencer> sequencer = mSequencer.promote();
+    if (sequencer != 0) {
+        sequencer->onCaptureAvailable(imgBuffer.timestamp, mCaptureHeap->mBuffers[0]);
     }
+
     return OK;
 }
 
diff --git a/services/camera/libcameraservice/camera2/ZslProcessor.cpp b/services/camera/libcameraservice/camera2/ZslProcessor.cpp
index 8906cd7..c7a679e 100644
--- a/services/camera/libcameraservice/camera2/ZslProcessor.cpp
+++ b/services/camera/libcameraservice/camera2/ZslProcessor.cpp
@@ -97,6 +97,10 @@
                 __FUNCTION__, handle);
     }
 
+    // Erase entire ZSL queue since we've now completed the capture and preview
+    // is stopped.
+    clearZslQueueLocked();
+
     mState = RUNNING;
 }
 
@@ -240,7 +244,6 @@
         dumpZslQueue(-1);
     }
 
-
     if (mZslQueueTail != mZslQueueHead) {
         CameraMetadata request;
         size_t index = mZslQueueTail;
@@ -312,6 +315,26 @@
     return OK;
 }
 
+status_t ZslProcessor::clearZslQueue() {
+    Mutex::Autolock l(mInputMutex);
+    // If in middle of capture, can't clear out queue
+    if (mState == LOCKED) return OK;
+
+    return clearZslQueueLocked();
+}
+
+status_t ZslProcessor::clearZslQueueLocked() {
+    for (size_t i = 0; i < mZslQueue.size(); i++) {
+        if (mZslQueue[i].buffer.mTimestamp != 0) {
+            mZslConsumer->releaseBuffer(mZslQueue[i].buffer);
+        }
+        mZslQueue.replaceAt(i);
+    }
+    mZslQueueHead = 0;
+    mZslQueueTail = 0;
+    return OK;
+}
+
 void ZslProcessor::dump(int fd, const Vector<String16>& args) const {
     Mutex::Autolock l(mInputMutex);
     dumpZslQueue(fd);
diff --git a/services/camera/libcameraservice/camera2/ZslProcessor.h b/services/camera/libcameraservice/camera2/ZslProcessor.h
index 268f4f5..96ad5fb 100644
--- a/services/camera/libcameraservice/camera2/ZslProcessor.h
+++ b/services/camera/libcameraservice/camera2/ZslProcessor.h
@@ -62,6 +62,7 @@
     int getReprocessStreamId() const;
 
     status_t pushToReprocess(int32_t requestId);
+    status_t clearZslQueue();
 
     void dump(int fd, const Vector<String16>& args) const;
   private:
@@ -111,6 +112,8 @@
     // Match up entries from frame list to buffers in ZSL queue
     void findMatchesLocked();
 
+    status_t clearZslQueueLocked();
+
     void dumpZslQueue(int id) const;
 };