Kore/app/src/main/java/org/xbmc/kore/jsonrpc/HostConnection.java

828 lines
32 KiB
Java

/*
* Copyright 2015 Synced Synapse. All rights reserved.
*
* 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.
*/
package org.xbmc.kore.jsonrpc;
import android.os.Handler;
import android.os.Process;
import android.util.Base64;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.xbmc.kore.host.HostInfo;
import org.xbmc.kore.jsonrpc.notification.Input;
import org.xbmc.kore.jsonrpc.notification.Player;
import org.xbmc.kore.jsonrpc.notification.System;
import org.xbmc.kore.utils.LogUtils;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.HttpURLConnection;
import java.net.InetSocketAddress;
import java.net.ProtocolException;
import java.net.Socket;
import java.net.URL;
import java.util.HashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* Class responsible for communicating with the host.
*/
public class HostConnection {
public static final String TAG = LogUtils.makeLogTag(HostConnection.class);
/**
* Communicate via TCP
*/
public static final int PROTOCOL_TCP = 0;
/**
* Communicate via HTTP
*/
public static final int PROTOCOL_HTTP = 1;
/**
* Interface that an observer must implement to be notified of player notifications
*/
public interface PlayerNotificationsObserver {
public void onPlay(Player.OnPlay notification);
public void onPause(Player.OnPause notification);
public void onSpeedChanged(Player.OnSpeedChanged notification);
public void onSeek(Player.OnSeek notification);
public void onStop(Player.OnStop notification);
}
/**
* Interface that an observer must implement to be notified of System notifications
*/
public interface SystemNotificationsObserver {
public void onQuit(System.OnQuit notification);
public void onRestart(System.OnRestart notification);
public void onSleep(System.OnSleep notification);
}
/**
* Interface that an observer must implement to be notified of Input notifications
*/
public interface InputNotificationsObserver {
public void onInputRequested(Input.OnInputRequested notification);
}
/**
* Host to connect too
*/
private final HostInfo hostInfo;
/**
* The protocol to use: {@link #PROTOCOL_HTTP} or {@link #PROTOCOL_TCP}
* This is initially obtained from the {@link HostInfo}, but can be later changed through
* {@link #setProtocol(int)}
*/
private int protocol;
private final ObjectMapper objectMapper = new ObjectMapper();
/**
* Socket used to communicate through TCP
*/
private Socket socket = null;
/**
* Listener {@link Thread} that will be listening on the TCP socket
*/
private Thread listenerThread = null;
/**
* {@link java.util.HashMap} that will hold the {@link MethodCallInfo} with the information
* necessary to respond to clients (TCP only)
*/
private final HashMap<String, MethodCallInfo<?>> clientCallbacks = new HashMap<String, MethodCallInfo<?>>();
/**
* The observers that will be notified of player notifications
*/
private final HashMap<PlayerNotificationsObserver, Handler> playerNotificationsObservers =
new HashMap<PlayerNotificationsObserver, Handler>();
/**
* The observers that will be notified of system notifications
*/
private final HashMap<SystemNotificationsObserver, Handler> systemNotificationsObservers =
new HashMap<SystemNotificationsObserver, Handler>();
/**
* The observers that will be notified of input notifications
*/
private final HashMap<InputNotificationsObserver, Handler> inputNotificationsObservers =
new HashMap<InputNotificationsObserver, Handler>();
private ExecutorService executorService;
private final int connectTimeout;
private static final int DEFAULT_CONNECT_TIMEOUT = 5000; // ms
private static final int TCP_READ_TIMEOUT = 30000; // ms
/**
* Creates a new host connection
* @param hostInfo Host info object
*/
public HostConnection(final HostInfo hostInfo) {
this(hostInfo, DEFAULT_CONNECT_TIMEOUT);
}
/**
* Creates a new host connection
* @param hostInfo Host info object
* @param connectTimeout Connection timeout in ms
*/
public HostConnection(final HostInfo hostInfo, int connectTimeout) {
this.hostInfo = hostInfo;
// Start with the default host protocol
this.protocol = hostInfo.getProtocol();
// Create a single threaded executor
this.executorService = Executors.newSingleThreadExecutor();
// Set timeout
this.connectTimeout = connectTimeout;
}
/**
* Returns this connection protocol
* @return {@link #PROTOCOL_HTTP} or {@link #PROTOCOL_TCP}
*/
public int getProtocol() {
return protocol;
}
/**
* Overrides the protocol for this connection
* @param protocol {@link #PROTOCOL_HTTP} or {@link #PROTOCOL_TCP}
*/
public void setProtocol(int protocol) {
if (!isValidProtocol(protocol)) {
throw new IllegalArgumentException("Invalid protocol specified.");
}
this.protocol = protocol;
}
public static boolean isValidProtocol(int protocol) {
return ((protocol == PROTOCOL_TCP) || (protocol == PROTOCOL_HTTP));
}
/**
* Registers an observer for player notifications
* @param observer The {@link PlayerNotificationsObserver}
*/
public void registerPlayerNotificationsObserver(PlayerNotificationsObserver observer,
Handler handler) {
playerNotificationsObservers.put(observer, handler);
}
/**
* Unregisters and observer from the player notifications
* @param observer The {@link PlayerNotificationsObserver} to unregister
*/
public void unregisterPlayerNotificationsObserver(PlayerNotificationsObserver observer) {
playerNotificationsObservers.remove(observer);
}
/**
* Registers an observer for system notifications
* @param observer The {@link SystemNotificationsObserver}
*/
public void registerSystemNotificationsObserver(SystemNotificationsObserver observer,
Handler handler) {
systemNotificationsObservers.put(observer, handler);
}
/**
* Unregisters and observer from the system notifications
* @param observer The {@link SystemNotificationsObserver}
*/
public void unregisterSystemNotificationsObserver(SystemNotificationsObserver observer) {
systemNotificationsObservers.remove(observer);
}
/**
* Registers an observer for input notifications
* @param observer The {@link InputNotificationsObserver}
*/
public void registerInputNotificationsObserver(InputNotificationsObserver observer,
Handler handler) {
inputNotificationsObservers.put(observer, handler);
}
/**
* Unregisters and observer from the input notifications
* @param observer The {@link InputNotificationsObserver}
*/
public void unregisterInputNotificationsObserver(InputNotificationsObserver observer) {
inputNotificationsObservers.remove(observer);
}
/**
* Calls the a method on the server
* This call is always asynchronous. The results will be posted, through the
* {@link ApiCallback callback} parameter, on the specified {@link android.os.Handler}.
*
* @param method Method object that represents the methood too call
* @param callback {@link ApiCallback} to post the response to
* @param handler {@link Handler} to invoke callbacks on
* @param <T> Method return type
*/
public <T> void execute(final ApiMethod<T> method, final ApiCallback<T> callback,
final Handler handler) {
LogUtils.LOGD(TAG, "Starting method execute. Method: " + method.getMethodName() +
" on host: " + hostInfo.getJsonRpcHttpEndpoint());
// Launch background thread
Runnable command = new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
if (protocol == PROTOCOL_HTTP) {
executeThroughHTTP(method, callback, handler);
} else {
executeThroughTcp(method, callback, handler);
}
}
};
executorService.execute(command);
//new Thread(command).start();
}
/**
* Sends the JSON RPC request through HTTP
*/
private <T> void executeThroughHTTP(final ApiMethod<T> method, final ApiCallback<T> callback,
final Handler handler) {
String jsonRequest = method.toJsonString();
try {
HttpURLConnection connection = openHttpConnection(hostInfo);
sendHttpRequest(connection, jsonRequest);
// Read response and convert it
final T result = method.resultFromJson(parseJsonResponse(readHttpResponse(connection)));
if ((handler != null) && (callback != null)) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onSucess(result);
}
});
}
} catch (final ApiException e) {
// Got an error, call error handler
if ((handler != null) && (callback != null)) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(e.getCode(), e.getMessage());
}
});
}
}
}
/**
* Sends the JSON RPC request through HTTP, and calls the callback with the raw response,
* not parsed into the internal representation.
* Useful for sync methods that don't want to incur the overhead of constructing the
* internal objects.
*
* @param method Method object that represents the method too call
* @param callback {@link ApiCallback} to post the response to. This will be the raw
* {@link ObjectNode} received
* @param handler {@link Handler} to invoke callbacks on
* @param <T> Method return type
*/
public <T> void executeRaw(final ApiMethod<T> method, final ApiCallback<ObjectNode> callback,
final Handler handler) {
String jsonRequest = method.toJsonString();
try {
HttpURLConnection connection = openHttpConnection(hostInfo);
sendHttpRequest(connection, jsonRequest);
// Read response and convert it
final ObjectNode result = parseJsonResponse(readHttpResponse(connection));
if ((handler != null) && (callback != null)) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onSucess(result);
}
});
}
} catch (final ApiException e) {
// Got an error, call error handler
if ((handler != null) && (callback != null)) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(e.getCode(), e.getMessage());
}
});
}
}
}
/**
* Auxiliary method to open a HTTP connection.
* This method calls connect() so that any errors are cathced
* @param hostInfo Host info
* @return Connection set up
* @throws ApiException
*/
private HttpURLConnection openHttpConnection(HostInfo hostInfo) throws ApiException {
try {
// LogUtils.LOGD(TAG, "Opening HTTP connection.");
HttpURLConnection connection = (HttpURLConnection) new URL(hostInfo.getJsonRpcHttpEndpoint()).openConnection();
connection.setRequestMethod("POST");
connection.setConnectTimeout(connectTimeout);
//connection.setReadTimeout(connectTimeout);
connection.setRequestProperty("Content-Type", "application/json");
connection.setDoOutput(true);
// http basic authorization
if ((hostInfo.getUsername() != null) && !hostInfo.getUsername().isEmpty() &&
(hostInfo.getPassword() != null) && !hostInfo.getPassword().isEmpty()) {
final String token = Base64.encodeToString((hostInfo.getUsername() + ":" +
hostInfo.getPassword()).getBytes(), Base64.DEFAULT);
connection.setRequestProperty("Authorization", "Basic " + token);
}
// Check the connection
connection.connect();
return connection;
} catch (ProtocolException e) {
// Won't try to catch this
LogUtils.LOGE(TAG, "Got protocol exception while opening HTTP connection.", e);
throw new RuntimeException(e);
} catch (IOException e) {
LogUtils.LOGW(TAG, "Failed to open HTTP connection.", e);
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_CONNECTING, e);
}
}
/**
* Send an HTTP POST request
* @param connection Open connection
* @param request Request to send
* @throws ApiException
*/
private void sendHttpRequest(HttpURLConnection connection, String request) throws ApiException {
try {
LogUtils.LOGD(TAG, "Sending request via HTTP: " + request);
OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream());
out.write(request);
out.flush();
out.close();
} catch (IOException e) {
LogUtils.LOGW(TAG, "Failed to send HTTP request.", e);
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_SENDING_REQUEST, e);
}
}
/**
* Reads the response from the server
* @param connection Connection
* @return Response read
* @throws ApiException
*/
private String readHttpResponse(HttpURLConnection connection) throws ApiException {
try {
// LogUtils.LOGD(TAG, "Reading HTTP response.");
int responseCode = connection.getResponseCode();
switch (responseCode) {
case 200:
// All ok, read response
BufferedReader in = new BufferedReader(new InputStreamReader(connection.getInputStream()));
StringBuilder response = new StringBuilder();
String inputLine;
while ((inputLine = in.readLine()) != null)
response.append(inputLine);
in.close();
LogUtils.LOGD(TAG, "HTTP response: " + response.toString());
return response.toString();
case 401:
LogUtils.LOGD(TAG, "HTTP response read error. Got a 401.");
throw new ApiException(ApiException.HTTP_RESPONSE_CODE_UNAUTHORIZED,
"Server returned response code: " + responseCode);
case 404:
LogUtils.LOGD(TAG, "HTTP response read error. Got a 404.");
throw new ApiException(ApiException.HTTP_RESPONSE_CODE_NOT_FOUND,
"Server returned response code: " + responseCode);
default:
LogUtils.LOGD(TAG, "HTTP response read error. Got: " + responseCode);
throw new ApiException(ApiException.HTTP_RESPONSE_CODE_UNKNOWN,
"Server returned response code: " + responseCode);
}
} catch (IOException e) {
LogUtils.LOGW(TAG, "Failed to read HTTP response.", e);
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_READING_RESPONSE, e);
}
}
/**
* Parses the JSON response from the server.
* If it is a valid result returns the JSON {@link com.fasterxml.jackson.databind.node.ObjectNode} that represents it.
* If it is an error (contains the error tag), returns an {@link ApiException} with the info.
* @param response JSON response
* @return {@link com.fasterxml.jackson.databind.node.ObjectNode} constructed
* @throws ApiException
*/
private ObjectNode parseJsonResponse(String response) throws ApiException {
// LogUtils.LOGD(TAG, "Parsing JSON response");
try {
ObjectNode jsonResponse = (ObjectNode) objectMapper.readTree(response);
if (jsonResponse.has(ApiMethod.ERROR_NODE)) {
throw new ApiException(ApiException.API_ERROR, jsonResponse);
}
if (!jsonResponse.has(ApiMethod.RESULT_NODE)) {
// Something strange is going on
throw new ApiException(ApiException.INVALID_JSON_RESPONSE_FROM_HOST,
"Result doesn't contain a result node.");
}
return jsonResponse;
} catch (JsonProcessingException e) {
LogUtils.LOGW(TAG, "Got an exception while parsing JSON response.", e);
throw new ApiException(ApiException.INVALID_JSON_RESPONSE_FROM_HOST, e);
} catch (IOException e) {
LogUtils.LOGW(TAG, "Got an exception while parsing JSON response.", e);
throw new ApiException(ApiException.INVALID_JSON_RESPONSE_FROM_HOST, e);
}
}
/**
* Sends the JSON RPC request through TCP
* Keeps a background thread running, listening on a socket
*/
private <T> void executeThroughTcp(final ApiMethod<T> method, final ApiCallback<T> callback,
final Handler handler) {
String methodId = String.valueOf(method.getId());
try {
// Save this method/callback for later response
// Check if a method with this id is already running and raise an error if so
synchronized (clientCallbacks) {
if (clientCallbacks.containsKey(methodId)) {
if ((handler != null) && (callback != null)) {
handler.post(new Runnable() {
@Override
public void run() {
callback.onError(ApiException.API_METHOD_WITH_SAME_ID_ALREADY_EXECUTING,
"A method with the same Id is already executing");
}
});
}
return;
}
clientCallbacks.put(methodId, new MethodCallInfo<T>(method, callback, handler));
}
// TODO: Validate if this shouldn't be enclosed by a synchronized.
if (socket == null) {
// Open connection to the server and setup reader thread
socket = openTcpConnection(hostInfo);
listenerThread = newListenerThread(socket);
listenerThread.start();
}
// Write request
sendTcpRequest(socket, method.toJsonString());
} catch (final ApiException e) {
callErrorCallback(methodId, e);
}
}
/**
* Auxiliary method to open the TCP {@link Socket}.
* This method calls connect() so that any errors are cathced
* @param hostInfo Host info
* @return Connection set up
* @throws ApiException
*/
private Socket openTcpConnection(HostInfo hostInfo) throws ApiException {
try {
LogUtils.LOGD(TAG, "Opening TCP connection on host: " + hostInfo.getAddress());
Socket socket = new Socket();
final InetSocketAddress address = new InetSocketAddress(hostInfo.getAddress(), hostInfo.getTcpPort());
// We're setting a read timeout on the socket, so no need to explicitly close it
socket.setSoTimeout(TCP_READ_TIMEOUT);
socket.connect(address, connectTimeout);
return socket;
} catch (IOException e) {
LogUtils.LOGW(TAG, "Failed to open TCP connection to host: " + hostInfo.getAddress());
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_CONNECTING, e);
}
}
/**
* Send a TCP request
* @param socket Socket to write to
* @param request Request to send
* @throws ApiException
*/
private void sendTcpRequest(Socket socket, String request) throws ApiException {
try {
LogUtils.LOGD(TAG, "Sending request via TCP: " + request);
BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
writer.write(request);
writer.flush();
} catch (Exception e) {
LogUtils.LOGW(TAG, "Failed to send TCP request.", e);
disconnect();
throw new ApiException(ApiException.IO_EXCEPTION_WHILE_SENDING_REQUEST, e);
}
}
private Thread newListenerThread(final Socket socket) {
// Launch a new thread to read from the socket
return new Thread(new Runnable() {
@Override
public void run() {
Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
try {
LogUtils.LOGD(TAG, "Starting socket listener thread...");
// We're going to read from the socket. This will be a blocking call and
// it will keep on going until disconnect() is called on this object.
// Note: Mind the objects used here: we use createParser because it doesn't
// close the socket after ObjectMapper.readTree.
JsonParser jsonParser = objectMapper.getFactory().createParser(socket.getInputStream());
ObjectNode jsonResponse;
while ((jsonResponse = objectMapper.readTree(jsonParser)) != null) {
LogUtils.LOGD(TAG, "Read from socket: " + jsonResponse.toString());
// LogUtils.LOGD_FULL(TAG, "Read from socket: " + jsonResponse.toString());
handleTcpResponse(jsonResponse);
}
} catch (JsonProcessingException e) {
LogUtils.LOGW(TAG, "Got an exception while parsing JSON response.", e);
callErrorCallback(null, new ApiException(ApiException.INVALID_JSON_RESPONSE_FROM_HOST, e));
} catch (IOException e) {
LogUtils.LOGW(TAG, "Error reading from socket.", e);
disconnect();
callErrorCallback(null, new ApiException(ApiException.IO_EXCEPTION_WHILE_READING_RESPONSE, e));
}
}
});
}
private <T> void handleTcpResponse(ObjectNode jsonResponse) {
if (!jsonResponse.has(ApiMethod.ID_NODE)) {
// It's a notification, notify observers
String notificationName = jsonResponse.get(ApiNotification.METHOD_NODE).asText();
ObjectNode params = (ObjectNode)jsonResponse.get(ApiNotification.PARAMS_NODE);
if (notificationName.equals(Player.OnPause.NOTIFICATION_NAME)) {
final Player.OnPause apiNotification = new Player.OnPause(params);
for (final PlayerNotificationsObserver observer :
playerNotificationsObservers.keySet()) {
Handler handler = playerNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onPause(apiNotification);
}
});
}
} else if (notificationName.equals(Player.OnPlay.NOTIFICATION_NAME)) {
final Player.OnPlay apiNotification = new Player.OnPlay(params);
for (final PlayerNotificationsObserver observer :
playerNotificationsObservers.keySet()) {
Handler handler = playerNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onPlay(apiNotification);
}
});
}
} else if (notificationName.equals(Player.OnSeek.NOTIFICATION_NAME)) {
final Player.OnSeek apiNotification = new Player.OnSeek(params);
for (final PlayerNotificationsObserver observer :
playerNotificationsObservers.keySet()) {
Handler handler = playerNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onSeek(apiNotification);
}
});
}
} else if (notificationName.equals(Player.OnSpeedChanged.NOTIFICATION_NAME)) {
final Player.OnSpeedChanged apiNotification = new Player.OnSpeedChanged(params);
for (final PlayerNotificationsObserver observer :
playerNotificationsObservers.keySet()) {
Handler handler = playerNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onSpeedChanged(apiNotification);
}
});
}
} else if (notificationName.equals(Player.OnStop.NOTIFICATION_NAME)) {
final Player.OnStop apiNotification = new Player.OnStop(params);
for (final PlayerNotificationsObserver observer :
playerNotificationsObservers.keySet()) {
Handler handler = playerNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onStop(apiNotification);
}
});
}
} else if (notificationName.equals(System.OnQuit.NOTIFICATION_NAME)) {
final System.OnQuit apiNotification = new System.OnQuit(params);
for (final SystemNotificationsObserver observer :
systemNotificationsObservers.keySet()) {
Handler handler = systemNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onQuit(apiNotification);
}
});
}
} else if (notificationName.equals(System.OnRestart.NOTIFICATION_NAME)) {
final System.OnRestart apiNotification = new System.OnRestart(params);
for (final SystemNotificationsObserver observer :
systemNotificationsObservers.keySet()) {
Handler handler = systemNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onRestart(apiNotification);
}
});
}
} else if (notificationName.equals(System.OnSleep.NOTIFICATION_NAME)) {
final System.OnSleep apiNotification = new System.OnSleep(params);
for (final SystemNotificationsObserver observer :
systemNotificationsObservers.keySet()) {
Handler handler = systemNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onSleep(apiNotification);
}
});
}
} else if (notificationName.equals(Input.OnInputRequested.NOTIFICATION_NAME)) {
final Input.OnInputRequested apiNotification = new Input.OnInputRequested(params);
for (final InputNotificationsObserver observer :
inputNotificationsObservers.keySet()) {
Handler handler = inputNotificationsObservers.get(observer);
handler.post(new Runnable() {
@Override
public void run() {
observer.onInputRequested(apiNotification);
}
});
}
}
LogUtils.LOGD(TAG, "Got a notification: " + jsonResponse.get("method").textValue());
} else {
String methodId = jsonResponse.get(ApiMethod.ID_NODE).asText();
if (jsonResponse.has(ApiMethod.ERROR_NODE)) {
// Error response
callErrorCallback(methodId, new ApiException(ApiException.API_ERROR, jsonResponse));
} else {
// Sucess response
final MethodCallInfo<?> methodCallInfo = clientCallbacks.get(methodId);
// LogUtils.LOGD(TAG, "Sending response to method: " + methodCallInfo.method.getMethodName());
if (methodCallInfo != null) {
try {
final T result = (T) methodCallInfo.method.resultFromJson(jsonResponse);
final ApiCallback<T> callback = (ApiCallback<T>) methodCallInfo.callback;
if ((methodCallInfo.handler != null) && (callback != null)) {
methodCallInfo.handler.post(new Runnable() {
@Override
public void run() {
callback.onSucess(result);
}
});
}
// We've replied, remove the client from the list
synchronized (clientCallbacks) {
clientCallbacks.remove(methodId);
}
} catch (ApiException e) {
callErrorCallback(methodId, e);
}
}
}
}
}
private <T> void callErrorCallback(String methodId, final ApiException error) {
synchronized (clientCallbacks) {
if (methodId != null) {
// Send error back to client
final MethodCallInfo<?> methodCallInfo = clientCallbacks.get(methodId);
if (methodCallInfo != null) {
final ApiCallback<T> callback = (ApiCallback<T>) methodCallInfo.callback;
if ((methodCallInfo.handler != null) && (callback != null)) {
methodCallInfo.handler.post(new Runnable() {
@Override
public void run() {
callback.onError(error.getCode(), error.getMessage());
}
});
}
}
clientCallbacks.remove(methodId);
} else {
// Notify all pending clients, it might be an error for them
for (String id : clientCallbacks.keySet()) {
final MethodCallInfo<?> methodCallInfo = clientCallbacks.get(id);
final ApiCallback<T> callback = (ApiCallback<T>)methodCallInfo.callback;
if ((methodCallInfo.handler != null) && (callback != null)) {
methodCallInfo.handler.post(new Runnable() {
@Override
public void run() {
callback.onError(error.getCode(), error.getMessage());
}
});
}
}
clientCallbacks.clear();
}
}
}
/**
* Cleans up used resources.
* This method should always be called if the protocoll used is TCP, so we can shutdown gracefully
*/
public void disconnect() {
if (protocol == PROTOCOL_HTTP)
return;
try {
if (socket != null) {
// Remove pending calls
if (!socket.isClosed()) {
socket.close();
}
}
} catch (IOException e) {
LogUtils.LOGE(TAG, "Error while closing socket", e);
} finally {
socket = null;
}
}
/**
* Helper class to aggregate a method, callback and handler
* @param <T>
*/
private static class MethodCallInfo<T> {
public final ApiMethod<T> method;
public final ApiCallback<T> callback;
public final Handler handler;
public MethodCallInfo(ApiMethod<T> method, ApiCallback<T> callback, Handler handler) {
this.method = method;
this.callback = callback;
this.handler = handler;
}
}
}