package com.mixpanel.android.viewcrawler;
import com.mixpanel.android.java_websocket.client.WebSocketClient;
import com.mixpanel.android.java_websocket.drafts.Draft_17;
import com.mixpanel.android.java_websocket.exceptions.NotSendableException;
import com.mixpanel.android.java_websocket.exceptions.WebsocketNotConnectedException;
import com.mixpanel.android.java_websocket.framing.Framedata;
import com.mixpanel.android.java_websocket.handshake.ServerHandshake;
import com.mixpanel.android.util.MPLog;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.BufferedOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import java.net.URI;
import java.nio.ByteBuffer;
/**
* EditorClient should handle all communication to and from the socket. It should be fairly naive and
* only know how to delegate messages to the ABHandler class.
*/
/* package */ class EditorConnection {
public class EditorConnectionException extends IOException {
private static final long serialVersionUID = -1884953175346045636L;
public EditorConnectionException(Throwable cause) {
super(cause.getMessage()); // IOException(cause) is only available in API level 9!
}
}
public interface Editor {
public void sendSnapshot(JSONObject message);
public void performEdit(JSONObject message);
public void clearEdits(JSONObject message);
public void bindEvents(JSONObject message);
public void setTweaks(JSONObject message);
public void sendDeviceInfo();
public void cleanup();
}
public EditorConnection(URI uri, Editor service, Socket sslSocket)
throws EditorConnectionException {
mService = service;
mURI = uri;
try {
mClient = new EditorClient(uri, CONNECT_TIMEOUT, sslSocket);
mClient.connectBlocking();
} catch (final InterruptedException e) {
throw new EditorConnectionException(e);
}
}
public boolean isValid() {
return !mClient.isClosed() && !mClient.isClosing() && !mClient.isFlushAndClose();
}
public BufferedOutputStream getBufferedOutputStream() {
return new BufferedOutputStream(new WebSocketOutputStream());
}
private class EditorClient extends WebSocketClient {
public EditorClient(URI uri, int connectTimeout, Socket sslSocket) throws InterruptedException {
super(uri, new Draft_17(), null, connectTimeout);
setSocket(sslSocket);
}
@Override
public void onOpen(ServerHandshake handshakedata) {
MPLog.v(LOGTAG, "Websocket connected");
}
@Override
public void onMessage(String message) {
MPLog.v(LOGTAG, "Received message from editor:\n" + message);
try {
final JSONObject messageJson = new JSONObject(message);
final String type = messageJson.getString("type");
if (type.equals("device_info_request")) {
mService.sendDeviceInfo();
} else if (type.equals("snapshot_request")) {
mService.sendSnapshot(messageJson);
} else if (type.equals("change_request")) {
mService.performEdit(messageJson);
} else if (type.equals("event_binding_request")) {
mService.bindEvents(messageJson);
} else if (type.equals("clear_request")) {
mService.clearEdits(messageJson);
} else if (type.equals("tweak_request")) {
mService.setTweaks(messageJson);
}
} catch (final JSONException e) {
MPLog.e(LOGTAG, "Bad JSON received:" + message, e);
}
}
@Override
public void onClose(int code, String reason, boolean remote) {
MPLog.v(LOGTAG, "WebSocket closed. Code: " + code + ", reason: " + reason + "\nURI: " + mURI);
mService.cleanup();
}
@Override
public void onError(Exception ex) {
if (ex != null && ex.getMessage() != null) {
MPLog.e(LOGTAG, "Websocket Error: " + ex.getMessage());
} else {
MPLog.e(LOGTAG, "Unknown websocket error occurred");
}
}
}
private class WebSocketOutputStream extends OutputStream {
@Override
public void write(int b)
throws EditorConnectionException {
// This should never be called.
final byte[] oneByte = new byte[1];
oneByte[0] = (byte) b;
write(oneByte, 0, 1);
}
@Override
public void write(byte[] b)
throws EditorConnectionException {
write(b, 0, b.length);
}
@Override
public void write(byte[] b, int off, int len)
throws EditorConnectionException {
final ByteBuffer message = ByteBuffer.wrap(b, off, len);
try {
mClient.sendFragmentedFrame(Framedata.Opcode.TEXT, message, false);
} catch (final WebsocketNotConnectedException e) {
throw new EditorConnectionException(e);
} catch (final NotSendableException e) {
throw new EditorConnectionException(e);
}
}
@Override
public void close()
throws EditorConnectionException {
try {
mClient.sendFragmentedFrame(Framedata.Opcode.TEXT, EMPTY_BYTE_BUFFER, true);
} catch (final WebsocketNotConnectedException e) {
throw new EditorConnectionException(e);
} catch (final NotSendableException e) {
throw new EditorConnectionException(e);
}
}
}
private final Editor mService;
private final EditorClient mClient;
private final URI mURI;
private static final int CONNECT_TIMEOUT = 5000;
private static final ByteBuffer EMPTY_BYTE_BUFFER = ByteBuffer.allocate(0);
private static final String LOGTAG = "MixpanelAPI.EditorCnctn";
}