diff --git a/KdeConnect/KdeConnect.iml b/KdeConnect/KdeConnect.iml index 79344f5d..22a89c1c 100644 --- a/KdeConnect/KdeConnect.iml +++ b/KdeConnect/KdeConnect.iml @@ -1,10 +1,11 @@ - + diff --git a/KdeConnect/src/main/AndroidManifest.xml b/KdeConnect/src/main/AndroidManifest.xml index ebbcddb5..c4eb6fe6 100644 --- a/KdeConnect/src/main/AndroidManifest.xml +++ b/KdeConnect/src/main/AndroidManifest.xml @@ -117,6 +117,23 @@ android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" /> + + + + + + + + + + + + + diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/BaseComputerLink.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/BaseLink.java similarity index 63% rename from KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/BaseComputerLink.java rename to KdeConnect/src/main/java/org/kde/kdeconnect/Backends/BaseLink.java index 36ffddda..59d13e8b 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/BaseComputerLink.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/BaseLink.java @@ -1,18 +1,26 @@ -package org.kde.kdeconnect.ComputerLinks; +package org.kde.kdeconnect.Backends; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Base64; -import org.kde.kdeconnect.LinkProviders.BaseLinkProvider; import org.kde.kdeconnect.NetworkPackage; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; import java.util.ArrayList; -public abstract class BaseComputerLink { +public abstract class BaseLink { private BaseLinkProvider linkProvider; private String deviceId; private ArrayList receivers = new ArrayList(); + protected PrivateKey privateKey; - protected BaseComputerLink(String deviceId, BaseLinkProvider linkProvider) { + protected BaseLink(String deviceId, BaseLinkProvider linkProvider) { this.linkProvider = linkProvider; this.deviceId = deviceId; } @@ -21,6 +29,10 @@ public abstract class BaseComputerLink { return deviceId; } + public void setPrivateKey(PrivateKey key) { + privateKey = key; + } + public BaseLinkProvider getLinkProvider() { return linkProvider; } @@ -46,5 +58,6 @@ public abstract class BaseComputerLink { //TO OVERRIDE public abstract boolean sendPackage(NetworkPackage np); + public abstract boolean sendPackageEncrypted(NetworkPackage np, PublicKey key); } diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/BaseLinkProvider.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/BaseLinkProvider.java similarity index 81% rename from KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/BaseLinkProvider.java rename to KdeConnect/src/main/java/org/kde/kdeconnect/Backends/BaseLinkProvider.java index 21d4f73e..b1d621b6 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/BaseLinkProvider.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/BaseLinkProvider.java @@ -1,8 +1,8 @@ -package org.kde.kdeconnect.LinkProviders; +package org.kde.kdeconnect.Backends; import android.util.Log; -import org.kde.kdeconnect.ComputerLinks.BaseComputerLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.NetworkPackage; import java.util.ArrayList; @@ -12,8 +12,8 @@ public abstract class BaseLinkProvider { private ArrayList connectionReceivers = new ArrayList(); public interface ConnectionReceiver { - public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link); - public void onConnectionLost(BaseComputerLink link); + public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link); + public void onConnectionLost(BaseLink link); } public void addConnectionReceiver(ConnectionReceiver cr) { @@ -25,13 +25,13 @@ public abstract class BaseLinkProvider { } //These two should be called when the provider links to a new computer - protected void connectionAccepted(NetworkPackage identityPackage, BaseComputerLink link) { + protected void connectionAccepted(NetworkPackage identityPackage, BaseLink link) { Log.i("LinkProvider", "connectionAccepted"); for(ConnectionReceiver cr : connectionReceivers) { cr.onConnectionReceived(identityPackage, link); } } - protected void connectionLost(BaseComputerLink link) { + protected void connectionLost(BaseLink link) { Log.i("LinkProvider", "connectionLost"); for(ConnectionReceiver cr : connectionReceivers) { cr.onConnectionLost(link); diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LanBackend/LanLink.java new file mode 100644 index 00000000..3cc7106c --- /dev/null +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -0,0 +1,184 @@ +package org.kde.kdeconnect.Backends.LanBackend; + +import android.content.SharedPreferences; +import android.preference.PreferenceManager; +import android.util.Base64; +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.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.NioSocketConnector; +import org.json.JSONObject; +import org.kde.kdeconnect.Backends.BaseLink; +import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.NetworkPackage; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.net.InetSocketAddress; +import java.net.ServerSocket; +import java.net.Socket; +import java.net.SocketAddress; +import java.nio.charset.Charset; +import java.security.KeyFactory; +import java.security.PrivateKey; +import java.security.PublicKey; +import java.security.spec.PKCS8EncodedKeySpec; + +public class LanLink extends BaseLink { + + private IoSession session = null; + + public void disconnect() { + Log.i("LanLink", "Disconnect: "+session.getRemoteAddress().toString()); + session.close(true); + } + + public LanLink(IoSession session, String deviceId, BaseLinkProvider linkProvider) { + super(deviceId, linkProvider); + this.session = session; + } + + private JSONObject sendPayload(final InputStream stream) { + + try { + + ServerSocket candidateServer = null; + boolean success = false; + int tcpPort = 1739; + while(!success) { + try { + candidateServer = new ServerSocket(); + candidateServer.bind(new InetSocketAddress(tcpPort)); + success = true; + } catch(Exception e) { + Log.e("LanLink", "Exception openning serversocket: "+e); + tcpPort++; + if (tcpPort >= 1764) return new JSONObject(); + } + } + JSONObject payloadTransferInfo = new JSONObject(); + payloadTransferInfo.put("port", tcpPort); + + final ServerSocket server = candidateServer; + new Thread(new Runnable() { + @Override + public void run() { + //TODO: Timeout when waiting for a connection and close the socket + OutputStream socket = null; + try { + socket = server.accept().getOutputStream(); + byte[] buffer = new byte[4096]; + int bytesRead; + Log.e("LanLink","Beginning to send payload"); + while ((bytesRead = stream.read(buffer)) != -1) { + //Log.e("ok",""+bytesRead); + socket.write(buffer, 0, bytesRead); + } + Log.e("LanLink","Finished sending payload"); + } catch(Exception e) { + e.printStackTrace(); + Log.e("LanLink", "Exception with payload upload socket"); + } finally { + if (socket != null) { + try { socket.close(); } catch(Exception e) { } + } + try { server.close(); } catch(Exception e) { } + } + } + }).start(); + + return payloadTransferInfo; + + } catch(Exception e) { + + e.printStackTrace(); + Log.e("LanLink", "Exception with payload upload socket"); + + return null; + } + + } + @Override + public boolean sendPackage(final NetworkPackage np) { + if (session == null) { + Log.e("LanLink", "sendPackage failed: not yet connected"); + return false; + } + + if (np.hasPayload()) { + JSONObject transferInfo = sendPayload(np.getPayload()); + np.setPayloadTransferInfo(transferInfo); + } + + session.write(np.serialize()); + + return true; + } + + @Override + public boolean sendPackageEncrypted(NetworkPackage np, PublicKey key) { + + if (session == null) { + Log.e("LanLink", "sendPackage failed: not yet connected"); + return false; + } + + try { + + if (np.hasPayload()) { + JSONObject transferInfo = sendPayload(np.getPayload()); + np.setPayloadTransferInfo(transferInfo); + } + + np.encrypt(key); + + session.write(np.serialize()); + + return true; + } catch (Exception e) { + e.printStackTrace(); + Log.e("LanLink", "Encryption exception"); + return false; + } + + } + + public void injectNetworkPackage(NetworkPackage np) { + + if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) { + + try { + np = np.decrypt(privateKey); + } catch(Exception e) { + e.printStackTrace(); + Log.e("onPackageReceived","Exception reading the key needed to decrypt the package"); + } + + } + + if (np.hasPayloadTransferInfo()) { + + try { + Socket socket = new Socket(); + int tcpPort = np.getPayloadTransferInfo().getInt("port"); + InetSocketAddress address = (InetSocketAddress)session.getRemoteAddress(); + socket.connect(new InetSocketAddress(address.getAddress(), tcpPort)); + np.setPayload(socket.getInputStream(), np.getPayloadSize()); + } catch (Exception e) { + e.printStackTrace(); + Log.e("LanLink", "Exception connecting to payload remote socket"); + } + + } + + packageReceived(np); + } +} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/LanLinkProvider.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java similarity index 71% rename from KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/LanLinkProvider.java rename to KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index 37c94249..5cd57e80 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/LanLinkProvider.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -1,4 +1,4 @@ -package org.kde.kdeconnect.LinkProviders; +package org.kde.kdeconnect.Backends.LanBackend; import android.content.Context; import android.os.AsyncTask; @@ -16,7 +16,7 @@ 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.ComputerLinks.LanComputerLink; +import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.NetworkPackage; import java.net.DatagramPacket; @@ -31,8 +31,8 @@ public class LanLinkProvider extends BaseLinkProvider { private final static int port = 1714; private Context context; - private HashMap visibleComputers = new HashMap(); - private HashMap nioSessions = new HashMap(); + private HashMap visibleComputers = new HashMap(); + private HashMap nioSessions = new HashMap(); private NioSocketAcceptor tcpAcceptor = null; private NioDatagramAcceptor udpAcceptor = null; @@ -41,7 +41,7 @@ public class LanLinkProvider extends BaseLinkProvider { @Override public void sessionClosed(IoSession session) throws Exception { - LanComputerLink brokenLink = nioSessions.remove(session.getId()); + LanLink brokenLink = nioSessions.remove(session.getId()); if (brokenLink != null) { connectionLost(brokenLink); brokenLink.disconnect(); @@ -62,14 +62,18 @@ public class LanLinkProvider extends BaseLinkProvider { String theMessage = (String) message; NetworkPackage np = NetworkPackage.unserialize(theMessage); - LanComputerLink prevLink = nioSessions.get(session.getId()); + LanLink prevLink = nioSessions.get(session.getId()); if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { + String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId"); if (np.getString("deviceId").equals(myId)) { return; } - LanComputerLink link = new LanComputerLink(session, np.getString("deviceId"), LanLinkProvider.this); + + //Log.e("LanLinkProvider", "Identity package received from "+np.getString("deviceName")); + + LanLink link = new LanLink(session, np.getString("deviceId"), LanLinkProvider.this); nioSessions.put(session.getId(),link); addLink(np, link); } else { @@ -90,77 +94,66 @@ public class LanLinkProvider extends BaseLinkProvider { //Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString()); - NetworkPackage np = null; - try { //We should receive a string thanks to the TextLineCodecFactory filter String theMessage = (String) message; - np = NetworkPackage.unserialize(theMessage); - } catch (Exception e) { - e.printStackTrace(); - Log.e("LanLinkProvider", "Could not unserialize package"); - } + final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage); - if (np != null) { - - final NetworkPackage identityPackage = np; - if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { + if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) { Log.e("LanLinkProvider", "1 Expecting an identity package"); return; } else { String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId"); - if (np.getString("deviceId").equals(myId)) { + if (identityPackage.getString("deviceId").equals(myId)) { return; } } Log.i("LanLinkProvider", "Identity package received, creating link"); - try { - final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress(); + final InetSocketAddress address = (InetSocketAddress) udpSession.getRemoteAddress(); - final NioSocketConnector connector = new NioSocketConnector(); - connector.setHandler(tcpHandler); - //TextLineCodecFactory will split incoming data delimited by the given string - connector.getFilterChain().addLast("codec", - new ProtocolCodecFilter( - new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX) - ) - ); - connector.getSessionConfig().setKeepAlive(true); + final NioSocketConnector connector = new NioSocketConnector(); + connector.setHandler(tcpHandler); + //TextLineCodecFactory will split incoming data delimited by the given string + connector.getFilterChain().addLast("codec", + new ProtocolCodecFilter( + new TextLineCodecFactory(Charset.defaultCharset(), LineDelimiter.UNIX, LineDelimiter.UNIX) + ) + ); + connector.getSessionConfig().setKeepAlive(true); - int tcpPort = np.getInt("tcpPort",port); - ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort)); - future.addListener(new IoFutureListener() { - @Override - public void operationComplete(IoFuture ioFuture) { - IoSession session = ioFuture.getSession(); + int tcpPort = identityPackage.getInt("tcpPort",port); + ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort)); + future.addListener(new IoFutureListener() { + @Override + public void operationComplete(IoFuture ioFuture) { + IoSession session = ioFuture.getSession(); - Log.i("LanLinkProvider", "Connection successful: " + session.isConnected()); + Log.i("LanLinkProvider", "Connection successful: " + session.isConnected()); - LanComputerLink link = new LanComputerLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this); + LanLink link = new LanLink(session, identityPackage.getString("deviceId"), LanLinkProvider.this); - NetworkPackage np2 = NetworkPackage.createIdentityPackage(context); - link.sendPackage(np2); + NetworkPackage np2 = NetworkPackage.createIdentityPackage(context); + link.sendPackage(np2); - nioSessions.put(session.getId(), link); - addLink(identityPackage, link); - } - }); - - } catch (Exception e) { - Log.e("LanLinkProvider","Exception!!"); - e.printStackTrace(); - } + nioSessions.put(session.getId(), link); + addLink(identityPackage, link); + } + }); + } catch (Exception e) { + Log.e("LanLinkProvider","Exception receiving udp package!!"); + e.printStackTrace(); } + } }; - private void addLink(NetworkPackage identityPackage, LanComputerLink link) { + private void addLink(NetworkPackage identityPackage, LanLink link) { String deviceId = identityPackage.getString("deviceId"); Log.i("LanLinkProvider","addLink to "+deviceId); - LanComputerLink oldLink = visibleComputers.get(deviceId); + LanLink oldLink = visibleComputers.get(deviceId); visibleComputers.put(deviceId, link); connectionAccepted(identityPackage, link); if (oldLink != null) { diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java new file mode 100644 index 00000000..96c056e9 --- /dev/null +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -0,0 +1,43 @@ +package org.kde.kdeconnect.Backends.LoopbackBackend; + +import android.util.Log; + +import org.kde.kdeconnect.Backends.BaseLink; +import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.NetworkPackage; + +import java.security.PublicKey; + +public class LoopbackLink extends BaseLink { + + public LoopbackLink(BaseLinkProvider linkProvider) { + super("loopback", linkProvider); + } + + @Override + public boolean sendPackage(NetworkPackage in) { + String s = in.serialize(); + NetworkPackage out= NetworkPackage.unserialize(s); + if (in.hasPayload()) out.setPayload(in.getPayload(), in.getPayloadSize()); + packageReceived(out); + return true; + } + + @Override + public boolean sendPackageEncrypted(NetworkPackage in, PublicKey key) { + try { + in.encrypt(key); + String s = in.serialize(); + NetworkPackage out= NetworkPackage.unserialize(s); + out.decrypt(privateKey); + packageReceived(out); + if (in.hasPayload()) out.setPayload(in.getPayload(), in.getPayloadSize()); + return true; + } catch(Exception e) { + e.printStackTrace(); + Log.e("LoopbackLink", "Encryption exception"); + return false; + } + + } +} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java new file mode 100644 index 00000000..766881b2 --- /dev/null +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java @@ -0,0 +1,43 @@ +package org.kde.kdeconnect.Backends.LoopbackBackend; + +import android.content.Context; + +import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.NetworkPackage; + +public class LoopbackLinkProvider extends BaseLinkProvider { + + private Context context; + + public LoopbackLinkProvider(Context context) { + this.context = context; + } + + @Override + public void onStart() { + onNetworkChange(); + } + + @Override + public void onStop() { + + } + + @Override + public void onNetworkChange() { + + NetworkPackage np = NetworkPackage.createIdentityPackage(context); + connectionAccepted(np, new LoopbackLink(this)); + + } + + @Override + public int getPriority() { + return 0; + } + + @Override + public String getName() { + return "LoopbackLinkProvider"; + } +} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/BackgroundService.java b/KdeConnect/src/main/java/org/kde/kdeconnect/BackgroundService.java index d2a26bdb..d68db4ac 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/BackgroundService.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/BackgroundService.java @@ -11,10 +11,10 @@ import android.preference.PreferenceManager; import android.util.Base64; import android.util.Log; -import org.kde.kdeconnect.ComputerLinks.BaseComputerLink; -import org.kde.kdeconnect.LinkProviders.BaseLinkProvider; -import org.kde.kdeconnect.LinkProviders.LanLinkProvider; -import org.kde.kdeconnect.LinkProviders.LoopbackLinkProvider; +import org.kde.kdeconnect.Backends.BaseLink; +import org.kde.kdeconnect.Backends.BaseLinkProvider; +import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; +import org.kde.kdeconnect.Backends.LoopbackBackend.LoopbackLinkProvider; import java.security.KeyPair; import java.security.KeyPairGenerator; @@ -54,7 +54,7 @@ public class BackgroundService extends Service { Set trustedDevices = preferences.getAll().keySet(); for(String deviceId : trustedDevices) { if (preferences.getBoolean(deviceId, false)) { - Device device = new Device(getBaseContext(), deviceId); + Device device = new Device(this, deviceId); devices.put(deviceId,device); device.addPairingCallback(devicePairingCallback); } @@ -81,7 +81,7 @@ public class BackgroundService extends Service { private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() { @Override - public void onConnectionReceived(final NetworkPackage identityPackage, final BaseComputerLink link) { + public void onConnectionReceived(final NetworkPackage identityPackage, final BaseLink link) { Log.i("BackgroundService", "Connection accepted!"); @@ -91,11 +91,11 @@ public class BackgroundService extends Service { if (device != null) { Log.i("BackgroundService", "addLink, known device: " + deviceId); - device.addLink(link); + device.addLink(identityPackage, link); } else { Log.i("BackgroundService", "addLink,unknown device: " + deviceId); String name = identityPackage.getString("deviceName"); - device = new Device(getBaseContext(), deviceId, name, link); + device = new Device(BackgroundService.this, identityPackage, link); devices.put(deviceId, device); device.addPairingCallback(devicePairingCallback); } @@ -104,7 +104,7 @@ public class BackgroundService extends Service { } @Override - public void onConnectionLost(BaseComputerLink link) { + public void onConnectionLost(BaseLink link) { Device d = devices.get(link.getDeviceId()); Log.i("onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); if (d != null) { diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/LanComputerLink.java b/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/LanComputerLink.java deleted file mode 100644 index a73ccb6b..00000000 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/LanComputerLink.java +++ /dev/null @@ -1,37 +0,0 @@ -package org.kde.kdeconnect.ComputerLinks; - -import android.util.Log; - -import org.apache.mina.core.session.IoSession; -import org.kde.kdeconnect.LinkProviders.BaseLinkProvider; -import org.kde.kdeconnect.NetworkPackage; - -public class LanComputerLink extends BaseComputerLink { - - private IoSession session = null; - - public void disconnect() { - Log.i("LanComputerLink","Disconnect: "+session.getRemoteAddress().toString()); - session.close(true); - } - - public LanComputerLink(IoSession session, String deviceId, BaseLinkProvider linkProvider) { - super(deviceId, linkProvider); - this.session = session; - } - - @Override - public boolean sendPackage(NetworkPackage np) { - if (session == null) { - Log.e("LanComputerLink","sendPackage failed: not yet connected"); - return false; - } else { - session.write(np.serialize()); - return true; - } - } - - public void injectNetworkPackage(NetworkPackage np) { - packageReceived(np); - } -} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/LoopbackComputerLink.java b/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/LoopbackComputerLink.java deleted file mode 100644 index fe43d101..00000000 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/ComputerLinks/LoopbackComputerLink.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.kde.kdeconnect.ComputerLinks; - -import android.util.Log; - -import org.apache.mina.core.session.IoSession; -import org.kde.kdeconnect.LinkProviders.BaseLinkProvider; -import org.kde.kdeconnect.NetworkPackage; - -public class LoopbackComputerLink extends BaseComputerLink { - - public LoopbackComputerLink(BaseLinkProvider linkProvider) { - super("loopback", linkProvider); - } - - @Override - public boolean sendPackage(NetworkPackage in) { - String s = in.serialize(); - NetworkPackage out= NetworkPackage.unserialize(s); - packageReceived(out); - return true; - } - -} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/Device.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Device.java index faef43af..7c6f0b62 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/Device.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Device.java @@ -15,7 +15,7 @@ import android.support.v4.app.NotificationCompat; import android.util.Base64; import android.util.Log; -import org.kde.kdeconnect.ComputerLinks.BaseComputerLink; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.UserInterface.PairActivity; @@ -34,7 +34,7 @@ import java.util.Set; import java.util.Timer; import java.util.TimerTask; -public class Device implements BaseComputerLink.PackageReceiver { +public class Device implements BaseLink.PackageReceiver { private Context context; @@ -42,6 +42,7 @@ public class Device implements BaseComputerLink.PackageReceiver { private String name; private PublicKey publicKey; private int notificationId; + private int protocolVersion; private enum PairStatus { NotPaired, @@ -61,7 +62,7 @@ public class Device implements BaseComputerLink.PackageReceiver { private ArrayList pairingCallback = new ArrayList(); private Timer pairingTimer; - private ArrayList links = new ArrayList(); + private ArrayList links = new ArrayList(); private HashMap plugins = new HashMap(); private HashMap failedPlugins = new HashMap(); @@ -77,6 +78,7 @@ public class Device implements BaseComputerLink.PackageReceiver { this.deviceId = deviceId; this.name = settings.getString("deviceName", "unknown device"); this.pairStatus = PairStatus.Paired; + this.protocolVersion = NetworkPackage.ProtocolVersion; //We don't know it yet try { byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0); @@ -90,18 +92,19 @@ public class Device implements BaseComputerLink.PackageReceiver { } //Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet - Device(Context context, String deviceId, String name, BaseComputerLink dl) { + Device(Context context, NetworkPackage np, BaseLink dl) { settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); //Log.e("Device","Constructor B"); this.context = context; - this.deviceId = deviceId; - this.name = name; + this.deviceId = np.getString("deviceId"); + this.name = np.getString("deviceName"); + this.protocolVersion = np.getInt("protocolVersion"); this.pairStatus = PairStatus.NotPaired; this.publicKey = null; - addLink(dl); + addLink(np, dl); } public String getName() { @@ -112,6 +115,10 @@ public class Device implements BaseComputerLink.PackageReceiver { return deviceId; } + //Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer + public int compareProtocolVersion() { + return protocolVersion - NetworkPackage.ProtocolVersion; + } @@ -158,23 +165,32 @@ public class Device implements BaseComputerLink.PackageReceiver { //Send our own public key NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); - boolean success = sendPackage(np); + sendPackage(np, new SendPackageFinishedCallback(){ - if (!success) { - for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_could_not_send_package)); - return; - } - - pairingTimer = new Timer(); - pairingTimer.schedule(new TimerTask() { @Override - public void run() { - for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_timed_out)); + public void sendSuccessful() { + pairingTimer = new Timer(); + pairingTimer.schedule(new TimerTask() { + @Override + public void run() { + for (PairingCallback cb : pairingCallback) { + cb.pairingFailed(context.getString(R.string.error_timed_out)); + } + pairStatus = PairStatus.NotPaired; + } + }, 20*1000); + pairStatus = PairStatus.Requested; + } + + @Override + public void sendFailed() { + for (PairingCallback cb : pairingCallback) { + cb.pairingFailed(context.getString(R.string.error_could_not_send_package)); + } pairStatus = PairStatus.NotPaired; } - }, 20*1000); - pairStatus = PairStatus.Requested; + }); } @@ -207,9 +223,7 @@ public class Device implements BaseComputerLink.PackageReceiver { //Send our own public key NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); - boolean success = sendPackage(np); - - if (!success) return; + sendPackage(np); //TODO: Set a callback pairStatus = PairStatus.Paired; @@ -255,15 +269,27 @@ public class Device implements BaseComputerLink.PackageReceiver { return !links.isEmpty(); } - public void addLink(BaseComputerLink link) { + public void addLink(NetworkPackage identityPackage, BaseLink link) { + + this.protocolVersion = identityPackage.getInt("protocolVersion"); links.add(link); + try { + SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context); + byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey", ""), 0); + PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); + link.setPrivateKey(privateKey); + } catch (Exception e) { + e.printStackTrace(); + Log.e("Device", "Exception reading our own private key"); //Should not happen + } + Log.i("Device","addLink "+link.getLinkProvider().getName()+" -> "+getName() + " active links: "+ links.size()); - Collections.sort(links, new Comparator() { + Collections.sort(links, new Comparator() { @Override - public int compare(BaseComputerLink o, BaseComputerLink o2) { + public int compare(BaseLink o, BaseLink o2) { return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority(); } }); @@ -275,7 +301,7 @@ public class Device implements BaseComputerLink.PackageReceiver { } } - public void removeLink(BaseComputerLink link) { + public void removeLink(BaseLink link) { link.removePackageReceiver(this); links.remove(link); Log.i("Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size()); @@ -398,23 +424,6 @@ public class Device implements BaseComputerLink.PackageReceiver { Log.e("onPackageReceived","Device not paired, ignoring package!"); } else { - if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_ENCRYPTED)) { - - try { - //TODO: Do not read the key every time - SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context); - byte[] privateKeyBytes = Base64.decode(globalSettings.getString("privateKey",""), 0); - PrivateKey privateKey = KeyFactory.getInstance("RSA").generatePrivate(new PKCS8EncodedKeySpec(privateKeyBytes)); - np = np.decrypt(privateKey); - } catch(Exception e) { - e.printStackTrace(); - Log.e("onPackageReceived","Exception reading the key needed to decrypt the package"); - } - - } else { - //TODO: The other side doesn't know that we are already paired, do something - Log.e("onPackageReceived","WARNING: Received unencrypted package from paired device!"); - } for (Plugin plugin : plugins.values()) { plugin.onPackageReceived(np); @@ -423,36 +432,46 @@ public class Device implements BaseComputerLink.PackageReceiver { } + public interface SendPackageFinishedCallback { + void sendSuccessful(); + void sendFailed(); + } - public boolean sendPackage(final NetworkPackage np) { + public void sendPackage(NetworkPackage np) { + sendPackage(np,null); + } - if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()) { - try { - np.encrypt(publicKey); - } catch(Exception e) { - e.printStackTrace(); - Log.e("Device","sendPackage exception - could not encrypt"); - } - } + public void sendPackage(final NetworkPackage np, final SendPackageFinishedCallback callback) { - new AsyncTask() { + new Thread(new Runnable() { @Override - protected Void doInBackground(Void... voids) { - for(BaseComputerLink link : links) { - //Log.e("sendPackage","Trying "+link.getLinkProvider().getName()); - if (link.sendPackage(np)) { - //Log.e("sent using", link.getLinkProvider().getName()); - return null; + public void run() { + + boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired()); + + //Log.e("sendPackage", "Sending..."); + + for(BaseLink link : links) { + + boolean success; + if (useEncryption) { + success = link.sendPackageEncrypted(np, publicKey); + } else { + success = link.sendPackage(np); + } + if (success) { + //Log.e("sendPackage", "Sent"); + if (callback != null) callback.sendSuccessful(); + return; } } + + if (callback != null) callback.sendFailed(); Log.e("sendPackage","Error: Package could not be sent ("+links.size()+" links available)"); - return null; + } - }.execute(); + }).start(); - //TODO: Detect when unable to send a package and try again somehow - - return !links.isEmpty(); } diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/LoopbackLinkProvider.java b/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/LoopbackLinkProvider.java deleted file mode 100644 index 8e7d23f8..00000000 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/LinkProviders/LoopbackLinkProvider.java +++ /dev/null @@ -1,65 +0,0 @@ -package org.kde.kdeconnect.LinkProviders; - -import android.content.Context; -import android.os.AsyncTask; -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.ComputerLinks.LanComputerLink; -import org.kde.kdeconnect.ComputerLinks.LoopbackComputerLink; -import org.kde.kdeconnect.NetworkPackage; - -import java.net.DatagramPacket; -import java.net.DatagramSocket; -import java.net.InetAddress; -import java.net.InetSocketAddress; -import java.nio.charset.Charset; -import java.util.HashMap; - -public class LoopbackLinkProvider extends BaseLinkProvider { - - private Context context; - - public LoopbackLinkProvider(Context context) { - this.context = context; - } - - @Override - public void onStart() { - onNetworkChange(); - } - - @Override - public void onStop() { - - } - - @Override - public void onNetworkChange() { - - NetworkPackage np = NetworkPackage.createIdentityPackage(context); - connectionAccepted(np, new LoopbackComputerLink(this)); - - } - - @Override - public int getPriority() { - return 0; - } - - @Override - public String getName() { - return "LoopbackLinkProvider"; - } -} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/NetworkPackage.java b/KdeConnect/src/main/java/org/kde/kdeconnect/NetworkPackage.java index daf35177..bf1843c6 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/NetworkPackage.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/NetworkPackage.java @@ -11,6 +11,10 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; import java.nio.charset.Charset; import java.security.PrivateKey; import java.security.PublicKey; @@ -20,7 +24,7 @@ import javax.crypto.Cipher; public class NetworkPackage { - public final static int ProtocolVersion = 3; + public final static int ProtocolVersion = 5; public final static String PACKAGE_TYPE_IDENTITY = "kdeconnect.identity"; public final static String PACKAGE_TYPE_PAIR = "kdeconnect.pair"; @@ -31,18 +35,26 @@ public class NetworkPackage { public final static String PACKAGE_TYPE_NOTIFICATION = "kdeconnect.notification"; public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard"; public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris"; + public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share"; private long mId; private String mType; private JSONObject mBody; + private InputStream mPayload; + private JSONObject mPayloadTransferInfo; + private int mPayloadSize; private NetworkPackage() { + } public NetworkPackage(String type) { mId = System.currentTimeMillis(); mType = type; mBody = new JSONObject(); + mPayload = null; + mPayloadSize = 0; + mPayloadTransferInfo = new JSONObject(); } public String getType() { @@ -94,36 +106,61 @@ public class NetworkPackage { public boolean has(String key) { return mBody.has(key); } + public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); } + public String serialize() { JSONObject jo = new JSONObject(); try { - jo.put("id",mId); - jo.put("type",mType); - jo.put("body",mBody); + jo.put("id", mId); + jo.put("type", mType); + jo.put("body", mBody); + if (hasPayload()) { + jo.put("payloadSize", mPayloadSize); + jo.put("payloadTransferInfo", mPayloadTransferInfo); + } } catch(Exception e) { + e.printStackTrace(); + Log.e("NetworkPackage", "Serialization exception"); } + //QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format. String json = jo.toString().replace("\\/","/")+"\n"; - //Log.e("NetworkPackage.serialize",json); + + if (!isEncrypted()) { + //Log.e("NetworkPackage.serialize", json); + } + return json; } static public NetworkPackage unserialize(String s) { - //Log.e("NetworkPackage.unserialize", s); + NetworkPackage np = new NetworkPackage(); try { JSONObject jo = new JSONObject(s); np.mId = jo.getLong("id"); np.mType = jo.getString("type"); np.mBody = jo.getJSONObject("body"); + if (jo.has("payloadSize")) { + np.mPayloadTransferInfo = jo.getJSONObject("payloadTransferInfo"); + np.mPayloadSize = jo.getInt("payloadSize"); + } else { + np.mPayloadTransferInfo = new JSONObject(); + np.mPayloadSize = 0; + } } catch (Exception e) { + e.printStackTrace(); + Log.e("NetworkPackage", "Unserialization exception unserializing "+s); return null; } + + if (!np.isEncrypted()) { + //Log.e("NetworkPackage.unserialize", s); + } + return np; } - - public void encrypt(PublicKey publicKey) throws Exception { String serialized = serialize(); @@ -177,7 +214,6 @@ public class NetworkPackage { return unserialize(decryptedJson); } - static public NetworkPackage createIdentityPackage(Context context) { NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY); @@ -208,4 +244,40 @@ public class NetworkPackage { } + public void setPayload(byte[] data) { + setPayload(new ByteArrayInputStream(data), data.length); + } + + public void setPayload(InputStream stream, int size) { + mPayload = stream; + mPayloadSize = size; + } + + /*public void setPayload(InputStream stream) { + setPayload(stream, -1); + }*/ + + public InputStream getPayload() { + return mPayload; + } + + public int getPayloadSize() { + return mPayloadSize; + } + + public boolean hasPayload() { + return (mPayload != null); + } + + public boolean hasPayloadTransferInfo() { + return (mPayloadTransferInfo.length() > 0); + } + + public JSONObject getPayloadTransferInfo() { + return mPayloadTransferInfo; + } + + public void setPayloadTransferInfo(JSONObject payloadTransferInfo) { + mPayloadTransferInfo = payloadTransferInfo; + } } diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java index e2b5e356..6389255c 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java @@ -13,10 +13,10 @@ import android.widget.SeekBar; import android.widget.Spinner; import android.widget.TextView; +import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.BackgroundService; -import org.kde.kdeconnect.ComputerLinks.BaseComputerLink; import org.kde.kdeconnect.Device; -import org.kde.kdeconnect.LinkProviders.BaseLinkProvider; +import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect_tp.R; @@ -121,12 +121,12 @@ public class MprisActivity extends Activity { BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { @Override - public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link) { + public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) { connectToPlugin(); } @Override - public void onConnectionLost(BaseComputerLink link) { + public void onConnectionLost(BaseLink link) { } }; diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java b/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java index a7d3627b..96f32f6b 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/Plugins/NotificationsPlugin/NotificationsPlugin.java @@ -172,10 +172,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver. @Override public void onNotificationPosted(StatusBarNotification statusBarNotification) { - onNotificationPosted(statusBarNotification, false); + sendNotification(statusBarNotification, false); } - public void onNotificationPosted(StatusBarNotification statusBarNotification, boolean requestAnswer) { + public void sendNotification(StatusBarNotification statusBarNotification, boolean requestAnswer) { Notification notification = statusBarNotification.getNotification(); NotificationId id = NotificationId.fromNotification(statusBarNotification); @@ -185,20 +185,21 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver. String packageName = statusBarNotification.getPackageName(); String appName = AppsHelper.appNameLookup(context, packageName); + //TODO: Add support for displaying app icons to desktop plasmoid and uncomment this piece of code + /* try { + //TODO: Scale down app icon if too big and compress as JPG Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName); Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon); ByteArrayOutputStream outStream = new ByteArrayOutputStream(); appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream); byte[] bitmapData = outStream.toByteArray(); - byte[] serializedBitmapData = Base64.encode(bitmapData, Base64.NO_WRAP); - String stringBitmapData = new String(serializedBitmapData, Charset.defaultCharset()); - //The icon is super big, better sending it as a file transfer when we support that - //np.set("base64icon", stringBitmapData); + np.setPayload(bitmapData); } catch(Exception e) { e.printStackTrace(); Log.e("NotificationsPlugin","Error retrieving icon"); } + */ np.set("id", id.serialize()); np.set("appName", appName == null? packageName : appName); @@ -224,7 +225,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver. private void sendCurrentNotifications(NotificationReceiver service) { StatusBarNotification[] notifications = service.getActiveNotifications(); for (StatusBarNotification notification : notifications) { - onNotificationPosted(notification, true); + sendNotification(notification, true); } } diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/DeviceItem.java b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/DeviceItem.java index b708b833..79bcdcce 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/DeviceItem.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/DeviceItem.java @@ -28,6 +28,17 @@ public class DeviceItem implements ListAdapter.Item { TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title); if (titleView != null) titleView.setText(device.getName()); + if (device.compareProtocolVersion() != 0) { + TextView summaryView = (TextView)v.findViewById(R.id.list_item_entry_summary); + summaryView.setVisibility(View.VISIBLE); + if (device.compareProtocolVersion() > 0) { + summaryView.setText(R.string.protocol_version_newer); + } else { + summaryView.setText(R.string.protocol_version_older); + } + } else { + v.findViewById(R.id.list_item_entry_summary).setVisibility(View.GONE); + } v.setOnClickListener(new View.OnClickListener() { @Override diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/EntryItem.java b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/EntryItem.java new file mode 100644 index 00000000..263006fb --- /dev/null +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/EntryItem.java @@ -0,0 +1,33 @@ +package org.kde.kdeconnect.UserInterface.List; + + +import android.app.Activity; +import android.content.Intent; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.TextView; + +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.UserInterface.DeviceActivity; +import org.kde.kdeconnect.UserInterface.PairActivity; +import org.kde.kdeconnect_tp.R; + +public class EntryItem implements ListAdapter.Item { + + private final String title; + + public EntryItem(String title) { + this.title = title; + } + + @Override + public View inflateView(LayoutInflater layoutInflater) { + View v = layoutInflater.inflate(R.layout.list_item_entry, null); + + TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title); + if (titleView != null) titleView.setText(title); + + return v; + } + +} diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/SectionItem.java b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/SectionItem.java index a5c0e05b..7e0124b8 100644 --- a/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/SectionItem.java +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/List/SectionItem.java @@ -13,6 +13,7 @@ public class SectionItem implements ListAdapter.Item { public SectionItem(String title) { this.title = title; + this.isEmpty = false; } @Override diff --git a/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/ShareToReceiver.java b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/ShareToReceiver.java new file mode 100644 index 00000000..a9f113cc --- /dev/null +++ b/KdeConnect/src/main/java/org/kde/kdeconnect/UserInterface/ShareToReceiver.java @@ -0,0 +1,276 @@ +package org.kde.kdeconnect.UserInterface; + +import android.content.ContentResolver; +import android.content.Intent; +import android.database.Cursor; +import android.net.Uri; +import android.os.Bundle; +import android.provider.MediaStore; +import android.support.v7.app.ActionBar; +import android.support.v7.app.ActionBarActivity; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ListView; + +import org.kde.kdeconnect.BackgroundService; +import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.NetworkPackage; +import org.kde.kdeconnect.UserInterface.List.EntryItem; +import org.kde.kdeconnect.UserInterface.List.ListAdapter; +import org.kde.kdeconnect.UserInterface.List.SectionItem; +import org.kde.kdeconnect_tp.R; + +import java.io.InputStream; +import java.net.URL; +import java.util.ArrayList; +import java.util.Collection; + + +public class ShareToReceiver extends ActionBarActivity { + + + // + // Action bar + // + + private MenuItem menuProgress; + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.main, menu); + menuProgress = menu.findItem(R.id.menu_progress); + return true; + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_refresh: + updateComputerList(); + BackgroundService.RunCommand(ShareToReceiver.this, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + service.onNetworkChange(); + } + }); + item.setVisible(false); + menuProgress.setVisible(true); + new Thread(new Runnable() { + @Override + public void run() { + try { Thread.sleep(1500); } catch (InterruptedException e) { } + runOnUiThread(new Runnable() { + @Override + public void run() { + menuProgress.setVisible(false); + item.setVisible(true); + } + }); + } + }).start(); + break; + default: + break; + } + return true; + } + + private void updateComputerList() { + + final Intent intent = getIntent(); + + String action = intent.getAction(); + if (!Intent.ACTION_SEND.equals(action) && !Intent.ACTION_SEND_MULTIPLE.equals(action)) { + finish(); + return; + } + + BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(final BackgroundService service) { + + Collection devices = service.getDevices().values(); + final ArrayList devicesList = new ArrayList(); + final ArrayList items = new ArrayList(); + + items.add(new SectionItem(getString(R.string.share_to))); + + for (Device d : devices) { + if (d.isReachable() && d.isPaired()) { + devicesList.add(d); + items.add(new EntryItem(d.getName())); + } + } + + runOnUiThread(new Runnable() { + @Override + public void run() { + ListView list = (ListView) findViewById(R.id.listView1); + list.setAdapter(new ListAdapter(ShareToReceiver.this, items)); + list.setOnItemClickListener(new AdapterView.OnItemClickListener() { + @Override + public void onItemClick(AdapterView adapterView, View view, int i, long l) { + + Device device = devicesList.get(i-1); //NOTE: -1 because of the title! + + Bundle extras = intent.getExtras(); + if (extras.containsKey(Intent.EXTRA_STREAM)) { + + try { + + ArrayList uriList; + if (!Intent.ACTION_SEND.equals(intent.getAction())) { + uriList = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM); + } else { + Uri uri = extras.getParcelable(Intent.EXTRA_STREAM); + uriList = new ArrayList(); + uriList.add(uri); + } + + queuedSendUriList(device, uriList); + + } catch (Exception e) { + Log.e(this.getClass().getName(), e.toString()); + } + + } else if (extras.containsKey(Intent.EXTRA_TEXT)) { + String text = extras.getString(Intent.EXTRA_TEXT); + boolean isUrl; + try { + new URL(text); + isUrl = true; + } catch(Exception e) { + isUrl = false; + } + NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE); + if (isUrl) { + np.set("url", text); + } else { + np.set("text", text); + } + device.sendPackage(np); + } + + + + finish(); + } + }); + } + }); + + } + }); + } + + private void queuedSendUriList(final Device device, final ArrayList uriList) { + try { + Uri uri = uriList.remove(0); + ContentResolver cr = getContentResolver(); + InputStream inputStream = cr.openInputStream(uri); + + NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_SHARE); + + String[] proj = { MediaStore.MediaColumns.DATA, MediaStore.MediaColumns.SIZE, MediaStore.MediaColumns.DISPLAY_NAME }; + Cursor cursor = managedQuery(uri, proj, null, null, null); + + int size = -1; + try { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.SIZE); + cursor.moveToFirst(); + size = cursor.getInt(column_index); + } catch(Exception e) { + e.printStackTrace(); + Log.e("ShareToReceiver", "Could not obtain file size"); + } + + //Log.e("ShareToReceiver", "Size "+size); + np.setPayload(inputStream, size); + + try { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATA); + cursor.moveToFirst(); + String path = cursor.getString(column_index); + np.set("filename", Uri.parse(path).getLastPathSegment()); + } catch(Exception _) { + try { + int column_index = cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME); + cursor.moveToFirst(); + String name = cursor.getString(column_index); + np.set("filename", name); + } catch (Exception e) { + e.printStackTrace(); + Log.e("ShareToReceiver", "Could not obtain file name"); + } + } + + device.sendPackage(np, new Device.SendPackageFinishedCallback() { + @Override + public void sendSuccessful() { + if (!uriList.isEmpty()) queuedSendUriList(device, uriList); + else Log.e("ShareToReceiver", "All files sent"); + } + + @Override + public void sendFailed() { + Log.e("ShareToReceiver", "Failed to send attachment"); + } + }); + } catch (Exception e) { + e.printStackTrace(); + Log.e("ShareToReceiver", "Failed to send attachment"); + } + + } + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + ActionBar actionBar = getSupportActionBar(); + actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_HOME | ActionBar.DISPLAY_SHOW_TITLE | ActionBar.DISPLAY_SHOW_CUSTOM); + + setContentView(R.layout.activity_main); + } + + + + @Override + protected void onStart() { + super.onStart(); + BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + service.onNetworkChange(); + service.setDeviceListChangedCallback(new BackgroundService.DeviceListChangedCallback() { + @Override + public void onDeviceListChanged() { + updateComputerList(); + } + }); + } + }); + } + + @Override + protected void onStop() { + BackgroundService.RunCommand(this, new BackgroundService.InstanceCallback() { + @Override + public void onServiceStart(BackgroundService service) { + service.setDeviceListChangedCallback(null); + } + }); + super.onStop(); + } + + @Override + protected void onResume() { + super.onResume(); + updateComputerList(); + } +} diff --git a/KdeConnect/src/main/res/layout/list_item_entry.xml b/KdeConnect/src/main/res/layout/list_item_entry.xml index 61f2ed2a..fd4fd3ba 100644 --- a/KdeConnect/src/main/res/layout/list_item_entry.xml +++ b/KdeConnect/src/main/res/layout/list_item_entry.xml @@ -6,43 +6,27 @@ android:layout_height="wrap_content" android:minHeight="?android:attr/listPreferredItemHeight" android:gravity="center_vertical" - android:paddingRight="?android:attr/scrollbarSize"> - - + android:singleLine="true" + android:textAppearance="?android:attr/textAppearanceLarge" + android:ellipsize="marquee" + android:fadingEdge="horizontal" + android:text="" /> - diff --git a/KdeConnect/src/main/res/values/strings.xml b/KdeConnect/src/main/res/values/strings.xml index 2d00ed92..7dc5a994 100644 --- a/KdeConnect/src/main/res/values/strings.xml +++ b/KdeConnect/src/main/res/values/strings.xml @@ -52,5 +52,9 @@ Previous Next Volume + Share To... + This device uses an old protocol version + This device uses a newer protocol version + diff --git a/kdeconnect-android.iml b/kdeconnect-android.iml index c2318111..94f1c61c 100644 --- a/kdeconnect-android.iml +++ b/kdeconnect-android.iml @@ -1,5 +1,5 @@ - +