Implement the source->sink(!) keep alive the wfd specs mandate...

Change-Id: I388d9911ebd243aa9c13a13ec3683e76898470a6
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
index 35d4414..a998dcd 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.cpp
@@ -116,8 +116,6 @@
 
                     err = mNetSession->createRTSPServer(
                             addr, port, notify, &mSessionID);
-
-                    ALOGI("createRTSPServer returned err %d", err);
                 } else {
                     err = -EINVAL;
                 }
@@ -154,7 +152,7 @@
 
                     mNetSession->destroySession(sessionID);
 
-                    mClientIPs.removeItem(sessionID);
+                    mClientInfos.removeItem(sessionID);
                     break;
                 }
 
@@ -167,10 +165,11 @@
                     CHECK(msg->findString("client-ip", &info.mRemoteIP));
                     CHECK(msg->findString("server-ip", &info.mLocalIP));
                     CHECK(msg->findInt32("server-port", &info.mLocalPort));
+                    info.mPlaybackSessionID = -1;
 
                     ALOGI("We now have a client (%d) connected.", sessionID);
 
-                    mClientIPs.add(sessionID, info);
+                    mClientInfos.add(sessionID, info);
 
                     status_t err = sendM1(sessionID);
                     CHECK_EQ(err, (status_t)OK);
@@ -284,6 +283,20 @@
             break;
         }
 
+        case kWhatKeepAlive:
+        {
+            int32_t sessionID;
+            CHECK(msg->findInt32("sessionID", &sessionID));
+
+            if (mClientInfos.indexOfKey(sessionID) < 0) {
+                // Obsolete event, client is already gone.
+                break;
+            }
+
+            sendM16(sessionID);
+            break;
+        }
+
         default:
             TRESPASS();
     }
@@ -366,7 +379,7 @@
     //   max-hres (none or 2 byte)
     //   max-vres (none or 2 byte)
 
-    const ClientInfo &info = mClientIPs.valueFor(sessionID);
+    const ClientInfo &info = mClientInfos.valueFor(sessionID);
 
     AString body = StringPrintf(
         "wfd_video_formats: "
@@ -425,6 +438,31 @@
     return OK;
 }
 
+status_t WifiDisplaySource::sendM16(int32_t sessionID) {
+    AString request = "GET_PARAMETER rtsp://localhost/wfd1.0 RTSP/1.0\r\n";
+    AppendCommonResponse(&request, mNextCSeq);
+
+    const ClientInfo &info = mClientInfos.valueFor(sessionID);
+    request.append(StringPrintf("Session: %d\r\n", info.mPlaybackSessionID));
+
+    request.append("Content-Length: 0\r\n");
+    request.append("\r\n");
+
+    status_t err =
+        mNetSession->sendRequest(sessionID, request.c_str(), request.size());
+
+    if (err != OK) {
+        return err;
+    }
+
+    registerResponseHandler(
+            sessionID, mNextCSeq, &WifiDisplaySource::onReceiveM16Response);
+
+    ++mNextCSeq;
+
+    return OK;
+}
+
 status_t WifiDisplaySource::onReceiveM1Response(
         int32_t sessionID, const sp<ParsedMessage> &msg) {
     int32_t statusCode;
@@ -481,6 +519,22 @@
     return OK;
 }
 
+status_t WifiDisplaySource::onReceiveM16Response(
+        int32_t sessionID, const sp<ParsedMessage> &msg) {
+    // If only the response was required to include a "Session:" header...
+
+    const ClientInfo &info = mClientInfos.valueFor(sessionID);
+
+    ssize_t index = mPlaybackSessions.indexOfKey(info.mPlaybackSessionID);
+    if (index >= 0) {
+        mPlaybackSessions.valueAt(index)->updateLiveness();
+
+        scheduleKeepAlive(sessionID);
+    }
+
+    return OK;
+}
+
 void WifiDisplaySource::scheduleReaper() {
     if (mReaperPending) {
         return;
@@ -490,6 +544,16 @@
     (new AMessage(kWhatReapDeadClients, id()))->post(kReaperIntervalUs);
 }
 
+void WifiDisplaySource::scheduleKeepAlive(int32_t sessionID) {
+    // We need to send updates at least 5 secs before the timeout is set to
+    // expire, make sure the timeout is greater than 5 secs to begin with.
+    CHECK_GT(kPlaybackSessionTimeoutUs, 5000000ll);
+
+    sp<AMessage> msg = new AMessage(kWhatKeepAlive, id());
+    msg->setInt32("sessionID", sessionID);
+    msg->post(kPlaybackSessionTimeoutUs - 5000000ll);
+}
+
 void WifiDisplaySource::onReceiveClientData(const sp<AMessage> &msg) {
     int32_t sessionID;
     CHECK(msg->findInt32("sessionID", &sessionID));
@@ -637,6 +701,14 @@
         int32_t sessionID,
         int32_t cseq,
         const sp<ParsedMessage> &data) {
+    ClientInfo *info = &mClientInfos.editValueFor(sessionID);
+    if (info->mPlaybackSessionID != -1) {
+        // 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;
+    }
+
     AString transport;
     if (!data->findString("transport", &transport)) {
         sendErrorResponse(sessionID, "400 Bad Request", cseq);
@@ -713,10 +785,8 @@
         return;
     }
 
-    const ClientInfo &info = mClientIPs.valueFor(sessionID);
-
     status_t err = playbackSession->init(
-            info.mRemoteIP.c_str(),
+            info->mRemoteIP.c_str(),
             clientRtp,
             clientRtcp,
             useInterleavedTCP);
@@ -739,6 +809,8 @@
 
     mPlaybackSessions.add(playbackSessionID, playbackSession);
 
+    info->mPlaybackSessionID = playbackSessionID;
+
     AString response = "RTSP/1.0 200 OK\r\n";
     AppendCommonResponse(&response, cseq, playbackSessionID);
 
@@ -770,10 +842,8 @@
     err = mNetSession->sendRequest(sessionID, response.c_str());
     CHECK_EQ(err, (status_t)OK);
 
-#if 0
-    // XXX the dongle does not currently send keep-alives.
     scheduleReaper();
-#endif
+    scheduleKeepAlive(sessionID);
 }
 
 void WifiDisplaySource::onPlayRequest(
@@ -949,6 +1019,11 @@
     for (;;) {
         int32_t playbackSessionID = rand();
 
+        if (playbackSessionID == -1) {
+            // reserved.
+            continue;
+        }
+
         for (size_t i = 0; i < mPlaybackSessions.size(); ++i) {
             if (mPlaybackSessions.keyAt(i) == playbackSessionID) {
                 continue;
diff --git a/media/libstagefright/wifi-display/source/WifiDisplaySource.h b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
index cd9939b..f56347d 100644
--- a/media/libstagefright/wifi-display/source/WifiDisplaySource.h
+++ b/media/libstagefright/wifi-display/source/WifiDisplaySource.h
@@ -49,6 +49,7 @@
         kWhatStop,
         kWhatReapDeadClients,
         kWhatPlaybackSessionNotify,
+        kWhatKeepAlive,
     };
 
     struct ResponseID {
@@ -79,8 +80,10 @@
         AString mRemoteIP;
         AString mLocalIP;
         int32_t mLocalPort;
+        int32_t mPlaybackSessionID;
     };
-    KeyedVector<int32_t, ClientInfo> mClientIPs;
+    // by sessionID.
+    KeyedVector<int32_t, ClientInfo> mClientInfos;
 
     bool mReaperPending;
 
@@ -94,6 +97,7 @@
     status_t sendM3(int32_t sessionID);
     status_t sendM4(int32_t sessionID);
     status_t sendM5(int32_t sessionID);
+    status_t sendM16(int32_t sessionID);
 
     status_t onReceiveM1Response(
             int32_t sessionID, const sp<ParsedMessage> &msg);
@@ -107,6 +111,9 @@
     status_t onReceiveM5Response(
             int32_t sessionID, const sp<ParsedMessage> &msg);
 
+    status_t onReceiveM16Response(
+            int32_t sessionID, const sp<ParsedMessage> &msg);
+
     void registerResponseHandler(
             int32_t sessionID, int32_t cseq, HandleRTSPResponseFunc func);
 
@@ -161,6 +168,7 @@
             AString *response, int32_t cseq, int32_t playbackSessionID = -1ll);
 
     void scheduleReaper();
+    void scheduleKeepAlive(int32_t sessionID);
 
     int32_t makeUniquePlaybackSessionID() const;