Search code examples
androidokhttpalexa-voice-service

OkHttp half-closed stream for AVS's downchannel on Android


I am using OkHttp v3.6.0 on Android for communicating with AVS v20160207. I am successful in communicating with AVS on the Events channel for both sending SpeechRecognizer Event and receiving the matching SpeechSynthesizer directive.

When establishing a connection to downchannel, I receive a successful response of HTTP 200 Success and then block on the stream to receive inbound data. When I ask Alexa to set a "timer for 5 seconds", I receive her prompt saying she will start the timer but I never receive any directives on the downchannel to tell me to set the timer up.

What's also interesting, as noted above, I receive a HTTP 200 success from the downchannel and then can block on the response.body().source(). exhausted(). But after 10 minutes of being blocked and not receiving anything, the stream is CLOSED and I receive the following exception:

Response with Error okhttp3.internal.http2.StreamResetException: stream was reset: CANCEL at okhttp3.internal.http2.Http2Stream$FramingSource.checkNotClosed(Http2Stream.java:436) at okhttp3.internal.http2.Http2Stream$FramingSource.read(Http2Stream.java:338) at okio.ForwardingSource.read(ForwardingSource.java:35) at okio.RealBufferedSource$1.read(RealBufferedSource.java:409) at java.io.InputStream.read(InputStream.java:101) at com.example.demo.alexaassistant.AlexaVoiceServices.interfaces.DownChannelRunnable.run(DownChannelRunnable.java:192) at java.lang.Thread.run(Thread.java:761)

Note that I have tried all of the suggestions found in this thread: Establishing a downchannel with Okhttp?

private static final long CONNECTION_POOL_TIMEOUT_MILLISECONDS = 60 * 60 * 1000;

        ConnectionPool connectionPool = new ConnectionPool(5,
            CONNECTION_POOL_TIMEOUT_MILLISECONDS, TimeUnit.MILLISECONDS);

        /**
         * Create a customized HTTP/2 interface.
         *
         * For the AVS's downchannel, we need to
         * disable the timeout on the read.
         */
        OkHttpClient downChannelClient = httpClient.newBuilder()
            .connectTimeout(0, TimeUnit.MILLISECONDS)  // 0 => no timeout.
            .readTimeout(0, TimeUnit.MILLISECONDS)
            .connectionPool(connectionPool)
            .build();

        final Request request = new Request.Builder()
            .url(url)
            .get()
            .addHeader("Authorization", "Bearer " + this.accessToken)
            .build();
        Log.d(TAG, "downchannel URL ==> " + request.url().toString());
        Log.d(TAG, "downchannel headers ==> " + request.headers().toString());

        Response response = null;
        try
        {
            currentCall = downChannelClient.newCall(request);
    response = currentCall.execute();
    BufferedSource bufferedSource = response.body().source();

    Log.i(TAG, "Downchannel ==> HTTP response code: " + response.code());

    Buffer buffer = new Buffer();

    while (!bufferedSource.exhausted())
    {
        Log.w(TAG, "downchannel received data!!!");
        bufferedSource.read(buffer, 8192);
        Log.d(TAG, "Size of data read: " + buffer.size());
    }

    Log.d(TAG, "Response: " + buffer.toString());
}
catch (IOException e)
{
    Log.d(TAG, "Response with Error", e);
}
finally
{
    if (response != null)
    {
        response.close();
    }
}

EDIT:

Amazon's documentation says that the client needs to have ONE connection to the server so POSTs and GETs streams are made to that one connection as well as the one downchannel in a half-closed stream state. Does OkHttp2 support this?


Solution

  • Finally I'm now receiving data from the server in the downchannel. What I do was SynchronizeState after I get the 200 response when create the downchannel. The doc say:

    1. To establish a downchannel stream your client must make a GET request to /{{API version}}/directives within 10 seconds of opening the connection with AVS. The request should look like this:

    2. After establishing the downchannel stream, your client must synchronize it’s components’ states with AVS. This requires making a POST request to /{{API version}}/events on a new event stream on the existing connection (Note: Do not open a new connection). This event stream should be closed when your client receives a response (directive). The following is an example SynchronizeState event:

    Hope this help. I'm now struggling trying to parse the result in the downchannel. Your code should by:

    Log.i(TAG, "Downchannel ==> HTTP response code: " + response.code());
    
    ...
    
    synchronizeState();
    
    ....
    
    Buffer buffer = new Buffer();
    
    while (!bufferedSource.exhausted())
    {
        Log.w(TAG, "downchannel received data!!!");
        bufferedSource.read(buffer, 8192);
        Log.d(TAG, "Size of data read: " + buffer.size());
    }