2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-09-02 15:15:09 +00:00

Initial netty implementation

This commit is contained in:
Vineet Garg
2015-06-19 04:00:27 +05:30
parent 79df72b80b
commit 2f16656aa0
3 changed files with 166 additions and 148 deletions

View File

@@ -11,7 +11,7 @@ apply plugin: 'com.android.application'
android { android {
compileSdkVersion 22 compileSdkVersion 22
buildToolsVersion '22.0.1' buildToolsVersion '22.0.0'
defaultConfig { defaultConfig {
minSdkVersion 9 minSdkVersion 9
targetSdkVersion 22 targetSdkVersion 22
@@ -54,8 +54,7 @@ dependencies {
compile 'org.apache.mina:mina-core:2.0.9' compile 'org.apache.mina:mina-core:2.0.9'
compile 'org.apache.sshd:sshd-core:0.8.0' compile 'org.apache.sshd:sshd-core:0.8.0'
compile 'org.bouncycastle:bcprov-jdk16:1.46' compile 'org.bouncycastle:bcprov-jdk16:1.46'
compile 'io.netty:netty-all:4.0.23.Final'
androidTestCompile 'org.mockito:mockito-core:1.10.19' androidTestCompile 'org.mockito:mockito-core:1.10.19'
// Because mockito has some problems with dex environment // Because mockito has some problems with dex environment

View File

@@ -42,19 +42,23 @@ import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
public class LanLink extends BaseLink { public class LanLink extends BaseLink {
private IoSession session = null; // private IoSession session = null;
private Channel session = null;
public void disconnect() { public void disconnect() {
if (session == null) { if (session == null) {
Log.e("KDE/LanLink", "Not yet connected"); Log.e("KDE/LanLink", "Not yet connected");
return; return;
} }
session.close(true); session.close();
} }
public LanLink(IoSession session, String deviceId, BaseLinkProvider linkProvider) { public LanLink(Channel session, String deviceId, BaseLinkProvider linkProvider) {
super(deviceId, linkProvider); super(deviceId, linkProvider);
this.session = session; this.session = session;
} }
@@ -86,14 +90,14 @@ public class LanLink extends BaseLink {
} }
//Send body of the network package //Send body of the network package
WriteFuture future = session.write(np.serialize()); ChannelFuture future = session.writeAndFlush(np.serialize()).sync();
future.awaitUninterruptibly(); if (!future.isSuccess()) {
if (!future.isWritten()) {
Log.e("KDE/sendPackage", "!future.isWritten()"); Log.e("KDE/sendPackage", "!future.isWritten()");
callback.sendFailure(future.getException()); callback.sendFailure(future.cause());
return; return;
} }
//Send payload //Send payload
if (server != null) { if (server != null) {
OutputStream socket = null; OutputStream socket = null;
@@ -173,7 +177,7 @@ public class LanLink extends BaseLink {
try { try {
socket = new Socket(); socket = new Socket();
int tcpPort = np.getPayloadTransferInfo().getInt("port"); int tcpPort = np.getPayloadTransferInfo().getInt("port");
InetSocketAddress address = (InetSocketAddress)session.getRemoteAddress(); InetSocketAddress address = (InetSocketAddress)session.remoteAddress();
socket.connect(new InetSocketAddress(address.getAddress(), tcpPort)); socket.connect(new InetSocketAddress(address.getAddress(), tcpPort));
np.setPayload(socket.getInputStream(), np.getPayloadSize()); np.setPayload(socket.getInputStream(), np.getPayloadSize());
} catch (Exception e) { } catch (Exception e) {

View File

@@ -25,32 +25,41 @@ import android.preference.PreferenceManager;
import android.support.v4.util.LongSparseArray; import android.support.v4.util.LongSparseArray;
import android.util.Log; import android.util.Log;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.future.IoFuture;
import org.apache.mina.core.future.IoFutureListener;
import org.apache.mina.core.service.IoHandler;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.transport.socket.nio.NioDatagramAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Device; import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity; import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
import java.net.DatagramPacket;
import java.net.DatagramSocket; import java.net.DatagramSocket;
import java.net.InetAddress; import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.charset.Charset;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Set;
import io.netty.bootstrap.Bootstrap;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.DatagramPacket;
import io.netty.channel.socket.nio.NioDatagramChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.Delimiters;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.logging.LogLevel;
import io.netty.handler.logging.LoggingHandler;
import io.netty.util.CharsetUtil;
public class LanLinkProvider extends BaseLinkProvider { public class LanLinkProvider extends BaseLinkProvider {
@@ -59,26 +68,29 @@ public class LanLinkProvider extends BaseLinkProvider {
private final Context context; private final Context context;
private final HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>(); private final HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>();
private final LongSparseArray<LanLink> nioSessions = new LongSparseArray<LanLink>(); private final LongSparseArray<LanLink> nioLinks = new LongSparseArray<LanLink>();
private final LongSparseArray<NioSocketConnector> nioConnectors = new LongSparseArray<NioSocketConnector>(); private final LongSparseArray<Channel> nioChannels = new LongSparseArray<Channel>();
private NioSocketAcceptor tcpAcceptor = null; private ServerBootstrap tcpBootstrap = null;
private NioDatagramAcceptor udpAcceptor = null; private Bootstrap udpBootstrap = null;
private Channel udpChannel = null;
private final IoHandler tcpHandler = new IoHandlerAdapter() { private class TcpHandler extends ChannelInboundHandlerAdapter{
@Override @Override
public void sessionClosed(IoSession session) throws Exception { public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
try { try {
long id = session.getId(); long id = ctx.channel().hashCode();
final LanLink brokenLink = nioSessions.get(id); final LanLink brokenLink = nioLinks.get(id);
NioSocketConnector connector = nioConnectors.get(id); Channel channel = nioChannels.get(id);
if (connector != null) { if (channel != null) {
connector.dispose(); channel.close();
nioConnectors.remove(id); nioChannels.remove(id);
} }
if (brokenLink != null) { if (brokenLink != null) {
nioSessions.remove(id); nioLinks.remove(id);
//Log.i("KDE/LanLinkProvider", "nioSessions.size(): " + nioSessions.size() + " (-)"); //Log.i("KDE/LanLinkProvider", "nioLinks.size(): " + nioLinks.size() + " (-)");
try { try {
brokenLink.disconnect(); brokenLink.disconnect();
} catch (Exception e) { } catch (Exception e) {
@@ -110,11 +122,12 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
} }
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
super.messageReceived(session, message);
//Log.e("LanLinkProvider","Incoming package, address: "+session.getRemoteAddress()).toString()); @Override
public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception {
super.channelRead(ctx, message);
// Log.e("LanLinkProvider","Incoming package, address: " + ctx.channel().remoteAddress());
// Log.e("LanLinkProvider","Received:"+message); // Log.e("LanLinkProvider","Received:"+message);
String theMessage = (String) message; String theMessage = (String) message;
@@ -134,12 +147,12 @@ public class LanLinkProvider extends BaseLinkProvider {
//Log.i("KDE/LanLinkProvider", "Identity package received from " + np.getString("deviceName")); //Log.i("KDE/LanLinkProvider", "Identity package received from " + np.getString("deviceName"));
LanLink link = new LanLink(session, np.getString("deviceId"), LanLinkProvider.this); LanLink link = new LanLink(ctx.channel(), np.getString("deviceId"), LanLinkProvider.this);
nioSessions.put(session.getId(),link); nioLinks.put(ctx.channel().hashCode(), link);
//Log.e("KDE/LanLinkProvider","nioSessions.size(): " + nioSessions.size()); //Log.i("KDE/LanLinkProvider","nioLinks.size(): " + nioLinks.size());
addLink(np, link); addLink(np, link);
} else { } else {
LanLink prevLink = nioSessions.get(session.getId()); LanLink prevLink = nioLinks.get(ctx.channel().hashCode());
if (prevLink == null) { if (prevLink == null) {
Log.e("KDE/LanLinkProvider","Expecting an identity package (A)"); Log.e("KDE/LanLinkProvider","Expecting an identity package (A)");
} else { } else {
@@ -148,18 +161,15 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
} }
}; }
private class UdpHandler extends SimpleChannelInboundHandler<DatagramPacket> {
private final IoHandler udpHandler = new IoHandlerAdapter() {
@Override @Override
public void messageReceived(IoSession udpSession, Object message) throws Exception { protected void channelRead0(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
super.messageReceived(udpSession, message);
//Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
try { try {
//We should receive a string thanks to the TextLineCodecFactory filter String theMessage = packet.content().toString(CharsetUtil.UTF_8);
String theMessage = (String) message;
final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage); final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage);
if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
@@ -172,30 +182,35 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
} }
//Log.i("KDE/LanLinkProvider", "Identity package received, creating link"); Log.i("KDE/LanLinkProvider", "Identity package received, creating link");
final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress();
final NioSocketConnector connector = new NioSocketConnector(); EventLoopGroup clientGroup = new NioEventLoopGroup();
connector.setHandler(tcpHandler);
connector.getSessionConfig().setKeepAlive(true);
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n
TextLineCodecFactory textLineFactory = new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX);
textLineFactory.setDecoderMaxLineLength(512*1024); //Allow to receive up to 512kb of data
connector.getFilterChain().addLast("codec", new ProtocolCodecFilter(textLineFactory));
int tcpPort = identityPackage.getInt("tcpPort", port);
final ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
future.addListener(new IoFutureListener<IoFuture>() {
@Override
public void operationComplete(IoFuture ioFuture) {
try{ try{
future.removeListener(this); Bootstrap b = new Bootstrap();
final IoSession session = ioFuture.getSession(); b.group(clientGroup);
Log.i("KDE/LanLinkProvider", "Connection successful: " + session.isConnected()); b.channel(NioSocketChannel.class);
b.handler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new TcpHandler());
}
});
int tcpPort = identityPackage.getInt("tcpPort", port);
final ChannelFuture channelFuture = b.connect(packet.sender().getAddress(), tcpPort).sync();
channelFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
channelFuture.removeListener(this);
final Channel channel = channelFuture.channel();
final LanLink link = new LanLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this); Log.i("KDE/LanLinkProvider", "Connection successful: " + channel.isActive());
final LanLink link = new LanLink(channel, identityPackage.getString("deviceId"), LanLinkProvider.this);
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -203,9 +218,9 @@ public class LanLinkProvider extends BaseLinkProvider {
link.sendPackage(np2,new Device.SendPackageStatusCallback() { link.sendPackage(np2,new Device.SendPackageStatusCallback() {
@Override @Override
protected void onSuccess() { protected void onSuccess() {
nioSessions.put(session.getId(), link); nioLinks.put(channel.hashCode(), link);
nioConnectors.put(session.getId(), connector); nioChannels.put(channel.hashCode(), channel);
//Log.e("KDE/LanLinkProvider","nioSessions.size(): " + nioSessions.size()); Log.i("KDE/LanLinkProvider","nioLinks.size(): " + nioLinks.size());
addLink(identityPackage, link); addLink(identityPackage, link);
} }
@@ -217,20 +232,19 @@ public class LanLinkProvider extends BaseLinkProvider {
} }
}).start(); }).start();
} catch (Exception e) { //If we don't catch it here, Mina will swallow it :/
e.printStackTrace();
Log.e("KDE/LanLinkProvider", "sessionClosed exception");
}
} }
}); });
} catch (Exception e) {
e.printStackTrace();
}
} catch (Exception e) { } catch (Exception e) {
Log.e("KDE/LanLinkProvider","Exception receiving udp package!!"); Log.e("KDE/LanLinkProvider","Exception receiving udp package!!");
e.printStackTrace(); e.printStackTrace();
} }
}
} }
};
private void addLink(NetworkPackage identityPackage, LanLink link) { private void addLink(NetworkPackage identityPackage, LanLink link) {
String deviceId = identityPackage.getString("deviceId"); String deviceId = identityPackage.getString("deviceId");
@@ -253,64 +267,59 @@ public class LanLinkProvider extends BaseLinkProvider {
this.context = context; this.context = context;
//This handles the case when I'm the new device in the network and somebody answers my introduction package EventLoopGroup bossGroup = new NioEventLoopGroup(1);
tcpAcceptor = new NioSocketAcceptor(); EventLoopGroup workerGroup = new NioEventLoopGroup();
tcpAcceptor.setHandler(tcpHandler); try{
tcpAcceptor.getSessionConfig().setKeepAlive(true); tcpBootstrap = new ServerBootstrap();
tcpAcceptor.getSessionConfig().setReuseAddress(true); tcpBootstrap.group(bossGroup, workerGroup);
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n tcpBootstrap.channel(NioServerSocketChannel.class);
TextLineCodecFactory textLineFactory = new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX); tcpBootstrap.option(ChannelOption.SO_BACKLOG, 100);
textLineFactory.setDecoderMaxLineLength(512*1024); //Allow to receive up to 512kb of data tcpBootstrap.handler(new LoggingHandler(LogLevel.INFO));
tcpAcceptor.getFilterChain().addLast("codec", new ProtocolCodecFilter(textLineFactory)); tcpBootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);
tcpBootstrap.childOption(ChannelOption.SO_REUSEADDR, true);
tcpBootstrap.childHandler(new ChannelInitializer<Channel>() {
@Override
protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new TcpHandler());
}
});
tcpBootstrap.bind(new InetSocketAddress(port)).sync();
}catch (Exception e) {
e.printStackTrace();
}
udpAcceptor = new NioDatagramAcceptor(); EventLoopGroup udpEventLoopGroup = new NioEventLoopGroup();
udpAcceptor.getSessionConfig().setReuseAddress(true); //Share port if existing try {
//TextLineCodecFactory will buffer incoming data and emit a message very time it finds a \n udpBootstrap = new Bootstrap();
//This one will have the default MaxLineLength of 1KB udpBootstrap.group(udpEventLoopGroup);
udpAcceptor.getFilterChain().addLast("codec", udpBootstrap.channel(NioDatagramChannel.class);
new ProtocolCodecFilter( udpBootstrap.option(ChannelOption.SO_BROADCAST, true);
new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX) udpBootstrap.handler(new ChannelInitializer<Channel>() {
) @Override
); protected void initChannel(Channel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
pipeline.addLast(new StringDecoder());
pipeline.addLast(new StringEncoder());
pipeline.addLast(new UdpHandler());
}
});
udpChannel = udpBootstrap.bind(new InetSocketAddress(port)).sync().channel();
}catch (Exception e){
e.printStackTrace();
}
} }
@Override @Override
public void onStart() { public void onStart() {
//This handles the case when I'm the existing device in the network and receive a "hello" UDP package Log.e("KDE/LanLinkProvider", "onStart");
Set<SocketAddress> addresses = udpAcceptor.getLocalAddresses();
for (SocketAddress address : addresses) {
Log.i("KDE/LanLinkProvider", "UDP unbind old address");
udpAcceptor.unbind(address);
}
//Log.i("KDE/LanLinkProvider", "UDP Bind.");
udpAcceptor.setHandler(udpHandler);
try {
udpAcceptor.bind(new InetSocketAddress(port));
} catch(Exception e) {
Log.e("KDE/LanLinkProvider", "Error: Could not bind udp socket");
e.printStackTrace();
}
boolean success = false;
int tcpPort = port;
while(!success) {
try {
tcpAcceptor.bind(new InetSocketAddress(tcpPort));
success = true;
} catch(Exception e) {
tcpPort++;
}
}
Log.i("KDE/LanLinkProvider","Using tcpPort "+tcpPort);
//I'm on a new network, let's be polite and introduce myself
final int finalTcpPort = tcpPort;
new Thread(new Runnable() { new Thread(new Runnable() {
@Override @Override
public void run() { public void run() {
@@ -324,7 +333,7 @@ public class LanLinkProvider extends BaseLinkProvider {
iplist.add("255.255.255.255"); //Default: broadcast. iplist.add("255.255.255.255"); //Default: broadcast.
NetworkPackage identity = NetworkPackage.createIdentityPackage(context); NetworkPackage identity = NetworkPackage.createIdentityPackage(context);
identity.set("tcpPort", finalTcpPort); identity.set("tcpPort", port);
DatagramSocket socket = null; DatagramSocket socket = null;
byte[] bytes = null; byte[] bytes = null;
try { try {
@@ -342,7 +351,7 @@ public class LanLinkProvider extends BaseLinkProvider {
for (String ipstr : iplist) { for (String ipstr : iplist) {
try { try {
InetAddress client = InetAddress.getByName(ipstr); InetAddress client = InetAddress.getByName(ipstr);
DatagramPacket packet = new DatagramPacket(bytes, bytes.length, client, port); java.net.DatagramPacket packet = new java.net.DatagramPacket(bytes, bytes.length, client, port);
socket.send(packet); socket.send(packet);
//Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+packet.getAddress()); //Log.i("KDE/LanLinkProvider","Udp identity package sent to address "+packet.getAddress());
} catch (Exception e) { } catch (Exception e) {
@@ -360,14 +369,14 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override @Override
public void onNetworkChange() { public void onNetworkChange() {
//Log.e("KDE/LanLinkProvider","onNetworkChange"); Log.e("KDE/LanLinkProvider","onNetworkChange");
//FilesHelper.LogOpenFileCount(); //FilesHelper.LogOpenFileCount();
//Keep existing connections open while unbinding the socket //Keep existing connections open while unbinding the socket
tcpAcceptor.setCloseOnDeactivation(false); // tcpAcceptor.setCloseOnDeactivation(false);
onStop(); // onStop();
tcpAcceptor.setCloseOnDeactivation(true); // tcpAcceptor.setCloseOnDeactivation(true);
//FilesHelper.LogOpenFileCount(); //FilesHelper.LogOpenFileCount();
@@ -378,8 +387,14 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override @Override
public void onStop() { public void onStop() {
udpAcceptor.unbind(); Log.e("KDE/LanLinkProvider", "onStop");
tcpAcceptor.unbind(); try {
udpBootstrap.group().shutdownGracefully().sync();
tcpBootstrap.group().shutdownGracefully().sync();
tcpBootstrap.childGroup().shutdownGracefully().sync();
}catch (Exception e){
e.printStackTrace();
}
} }
@Override @Override