2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 18:07:55 +00:00

Merge branch 'filetransfer'

This commit is contained in:
Albert Vaca 2013-10-01 03:29:51 +02:00
commit f8f58ef2a2
23 changed files with 887 additions and 318 deletions

View File

@ -1,10 +1,11 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4"> <module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$/.." external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="FacetManager"> <component name="FacetManager">
<facet type="android" name="Android"> <facet type="android" name="Android">
<configuration> <configuration>
<option name="SELECTED_BUILD_VARIANT" value="Debug" /> <option name="SELECTED_BUILD_VARIANT" value="Debug" />
<option name="ASSEMBLE_TASK_NAME" value="assembleDebug" /> <option name="ASSEMBLE_TASK_NAME" value="assembleDebug" />
<option name="COMPILE_JAVA_TASK_NAME" value="compileDebug" />
<option name="ASSEMBLE_TEST_TASK_NAME" value="assembleTest" /> <option name="ASSEMBLE_TEST_TASK_NAME" value="assembleTest" />
<option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" /> <option name="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
<option name="ALLOW_USER_CONFIGURATION" value="false" /> <option name="ALLOW_USER_CONFIGURATION" value="false" />
@ -60,7 +61,6 @@
<excludeFolder url="file://$MODULE_DIR$/build/bundles" /> <excludeFolder url="file://$MODULE_DIR$/build/bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/classes" /> <excludeFolder url="file://$MODULE_DIR$/build/classes" />
<excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" /> <excludeFolder url="file://$MODULE_DIR$/build/dependency-cache" />
<excludeFolder url="file://$MODULE_DIR$/build/exploded-bundles" />
<excludeFolder url="file://$MODULE_DIR$/build/incremental" /> <excludeFolder url="file://$MODULE_DIR$/build/incremental" />
<excludeFolder url="file://$MODULE_DIR$/build/libs" /> <excludeFolder url="file://$MODULE_DIR$/build/libs" />
<excludeFolder url="file://$MODULE_DIR$/build/manifests" /> <excludeFolder url="file://$MODULE_DIR$/build/manifests" />
@ -70,10 +70,10 @@
<orderEntry type="jdk" jdkName="Android 4.3 Platform" jdkType="Android SDK" /> <orderEntry type="jdk" jdkName="Android 4.3 Platform" jdkType="Android SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" exported="" name="android-support-v4" level="project" /> <orderEntry type="library" exported="" name="android-support-v4" level="project" />
<orderEntry type="library" exported="" name="mina-core-2.0.7" level="project" />
<orderEntry type="library" exported="" name="slf4j-api-1.6.6" level="project" />
<orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" /> <orderEntry type="library" exported="" name="support-v4-18.0.0" level="project" />
<orderEntry type="library" exported="" name="ComAndroidSupportAppcompatV71800.aar" level="project" /> <orderEntry type="library" exported="" name="ComAndroidSupportAppcompatV71800.aar" level="project" />
<orderEntry type="library" exported="" name="slf4j-api-1.6.6" level="project" />
<orderEntry type="library" exported="" name="mina-core-2.0.7" level="project" />
</component> </component>
</module> </module>

View File

@ -117,6 +117,23 @@
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" /> android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
</activity> </activity>
<activity
android:theme="@style/Theme.AppCompat"
android:name="org.kde.kdeconnect.UserInterface.ShareToReceiver"
android:label="KDE Connect"
>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND_MULTIPLE" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="*/*" />
</intent-filter>
</activity>
<service android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver" <service android:name="org.kde.kdeconnect.Plugins.NotificationsPlugin.NotificationReceiver"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE"> android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter> <intent-filter>

View File

@ -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 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; import java.util.ArrayList;
public abstract class BaseComputerLink { public abstract class BaseLink {
private BaseLinkProvider linkProvider; private BaseLinkProvider linkProvider;
private String deviceId; private String deviceId;
private ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>(); private ArrayList<PackageReceiver> receivers = new ArrayList<PackageReceiver>();
protected PrivateKey privateKey;
protected BaseComputerLink(String deviceId, BaseLinkProvider linkProvider) { protected BaseLink(String deviceId, BaseLinkProvider linkProvider) {
this.linkProvider = linkProvider; this.linkProvider = linkProvider;
this.deviceId = deviceId; this.deviceId = deviceId;
} }
@ -21,6 +29,10 @@ public abstract class BaseComputerLink {
return deviceId; return deviceId;
} }
public void setPrivateKey(PrivateKey key) {
privateKey = key;
}
public BaseLinkProvider getLinkProvider() { public BaseLinkProvider getLinkProvider() {
return linkProvider; return linkProvider;
} }
@ -46,5 +58,6 @@ public abstract class BaseComputerLink {
//TO OVERRIDE //TO OVERRIDE
public abstract boolean sendPackage(NetworkPackage np); public abstract boolean sendPackage(NetworkPackage np);
public abstract boolean sendPackageEncrypted(NetworkPackage np, PublicKey key);
} }

View File

@ -1,8 +1,8 @@
package org.kde.kdeconnect.LinkProviders; package org.kde.kdeconnect.Backends;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.NetworkPackage; import org.kde.kdeconnect.NetworkPackage;
import java.util.ArrayList; import java.util.ArrayList;
@ -12,8 +12,8 @@ public abstract class BaseLinkProvider {
private ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>(); private ArrayList<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
public interface ConnectionReceiver { public interface ConnectionReceiver {
public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link); public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link);
public void onConnectionLost(BaseComputerLink link); public void onConnectionLost(BaseLink link);
} }
public void addConnectionReceiver(ConnectionReceiver cr) { 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 //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"); Log.i("LinkProvider", "connectionAccepted");
for(ConnectionReceiver cr : connectionReceivers) { for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionReceived(identityPackage, link); cr.onConnectionReceived(identityPackage, link);
} }
} }
protected void connectionLost(BaseComputerLink link) { protected void connectionLost(BaseLink link) {
Log.i("LinkProvider", "connectionLost"); Log.i("LinkProvider", "connectionLost");
for(ConnectionReceiver cr : connectionReceivers) { for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionLost(link); cr.onConnectionLost(link);

View File

@ -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);
}
}

View File

@ -1,4 +1,4 @@
package org.kde.kdeconnect.LinkProviders; package org.kde.kdeconnect.Backends.LanBackend;
import android.content.Context; import android.content.Context;
import android.os.AsyncTask; 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.NioDatagramAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.apache.mina.transport.socket.nio.NioSocketConnector; 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 org.kde.kdeconnect.NetworkPackage;
import java.net.DatagramPacket; import java.net.DatagramPacket;
@ -31,8 +31,8 @@ public class LanLinkProvider extends BaseLinkProvider {
private final static int port = 1714; private final static int port = 1714;
private Context context; private Context context;
private HashMap<String, LanComputerLink> visibleComputers = new HashMap<String, LanComputerLink>(); private HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>();
private HashMap<Long, LanComputerLink> nioSessions = new HashMap<Long, LanComputerLink>(); private HashMap<Long, LanLink> nioSessions = new HashMap<Long, LanLink>();
private NioSocketAcceptor tcpAcceptor = null; private NioSocketAcceptor tcpAcceptor = null;
private NioDatagramAcceptor udpAcceptor = null; private NioDatagramAcceptor udpAcceptor = null;
@ -41,7 +41,7 @@ public class LanLinkProvider extends BaseLinkProvider {
@Override @Override
public void sessionClosed(IoSession session) throws Exception { public void sessionClosed(IoSession session) throws Exception {
LanComputerLink brokenLink = nioSessions.remove(session.getId()); LanLink brokenLink = nioSessions.remove(session.getId());
if (brokenLink != null) { if (brokenLink != null) {
connectionLost(brokenLink); connectionLost(brokenLink);
brokenLink.disconnect(); brokenLink.disconnect();
@ -62,14 +62,18 @@ public class LanLinkProvider extends BaseLinkProvider {
String theMessage = (String) message; String theMessage = (String) message;
NetworkPackage np = NetworkPackage.unserialize(theMessage); 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)) { if (np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId"); String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
if (np.getString("deviceId").equals(myId)) { if (np.getString("deviceId").equals(myId)) {
return; 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); nioSessions.put(session.getId(),link);
addLink(np, link); addLink(np, link);
} else { } else {
@ -90,33 +94,23 @@ public class LanLinkProvider extends BaseLinkProvider {
//Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString()); //Log.e("LanLinkProvider", "Udp message received (" + message.getClass() + ") " + message.toString());
NetworkPackage np = null;
try { try {
//We should receive a string thanks to the TextLineCodecFactory filter //We should receive a string thanks to the TextLineCodecFactory filter
String theMessage = (String) message; String theMessage = (String) message;
np = NetworkPackage.unserialize(theMessage); final NetworkPackage identityPackage = NetworkPackage.unserialize(theMessage);
} catch (Exception e) {
e.printStackTrace();
Log.e("LanLinkProvider", "Could not unserialize package");
}
if (np != null) { if (!identityPackage.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
final NetworkPackage identityPackage = np;
if (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_IDENTITY)) {
Log.e("LanLinkProvider", "1 Expecting an identity package"); Log.e("LanLinkProvider", "1 Expecting an identity package");
return; return;
} else { } else {
String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId"); String myId = NetworkPackage.createIdentityPackage(context).getString("deviceId");
if (np.getString("deviceId").equals(myId)) { if (identityPackage.getString("deviceId").equals(myId)) {
return; return;
} }
} }
Log.i("LanLinkProvider", "Identity package received, creating link"); 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(); final NioSocketConnector connector = new NioSocketConnector();
@ -129,7 +123,7 @@ public class LanLinkProvider extends BaseLinkProvider {
); );
connector.getSessionConfig().setKeepAlive(true); connector.getSessionConfig().setKeepAlive(true);
int tcpPort = np.getInt("tcpPort",port); int tcpPort = identityPackage.getInt("tcpPort",port);
ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort)); ConnectFuture future = connector.connect(new InetSocketAddress(address.getAddress(), tcpPort));
future.addListener(new IoFutureListener<IoFuture>() { future.addListener(new IoFutureListener<IoFuture>() {
@Override @Override
@ -138,7 +132,7 @@ public class LanLinkProvider extends BaseLinkProvider {
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); NetworkPackage np2 = NetworkPackage.createIdentityPackage(context);
link.sendPackage(np2); link.sendPackage(np2);
@ -149,18 +143,17 @@ public class LanLinkProvider extends BaseLinkProvider {
}); });
} catch (Exception e) { } catch (Exception e) {
Log.e("LanLinkProvider","Exception!!"); Log.e("LanLinkProvider","Exception receiving udp package!!");
e.printStackTrace(); e.printStackTrace();
} }
} }
}
}; };
private void addLink(NetworkPackage identityPackage, LanComputerLink link) { private void addLink(NetworkPackage identityPackage, LanLink link) {
String deviceId = identityPackage.getString("deviceId"); String deviceId = identityPackage.getString("deviceId");
Log.i("LanLinkProvider","addLink to "+deviceId); Log.i("LanLinkProvider","addLink to "+deviceId);
LanComputerLink oldLink = visibleComputers.get(deviceId); LanLink oldLink = visibleComputers.get(deviceId);
visibleComputers.put(deviceId, link); visibleComputers.put(deviceId, link);
connectionAccepted(identityPackage, link); connectionAccepted(identityPackage, link);
if (oldLink != null) { if (oldLink != null) {

View File

@ -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;
}
}
}

View File

@ -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";
}
}

View File

@ -11,10 +11,10 @@ import android.preference.PreferenceManager;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.LinkProviders.BaseLinkProvider; import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.LinkProviders.LanLinkProvider; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
import org.kde.kdeconnect.LinkProviders.LoopbackLinkProvider; import org.kde.kdeconnect.Backends.LoopbackBackend.LoopbackLinkProvider;
import java.security.KeyPair; import java.security.KeyPair;
import java.security.KeyPairGenerator; import java.security.KeyPairGenerator;
@ -54,7 +54,7 @@ public class BackgroundService extends Service {
Set<String> trustedDevices = preferences.getAll().keySet(); Set<String> trustedDevices = preferences.getAll().keySet();
for(String deviceId : trustedDevices) { for(String deviceId : trustedDevices) {
if (preferences.getBoolean(deviceId, false)) { if (preferences.getBoolean(deviceId, false)) {
Device device = new Device(getBaseContext(), deviceId); Device device = new Device(this, deviceId);
devices.put(deviceId,device); devices.put(deviceId,device);
device.addPairingCallback(devicePairingCallback); device.addPairingCallback(devicePairingCallback);
} }
@ -81,7 +81,7 @@ public class BackgroundService extends Service {
private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() { private BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
@Override @Override
public void onConnectionReceived(final NetworkPackage identityPackage, final BaseComputerLink link) { public void onConnectionReceived(final NetworkPackage identityPackage, final BaseLink link) {
Log.i("BackgroundService", "Connection accepted!"); Log.i("BackgroundService", "Connection accepted!");
@ -91,11 +91,11 @@ public class BackgroundService extends Service {
if (device != null) { if (device != null) {
Log.i("BackgroundService", "addLink, known device: " + deviceId); Log.i("BackgroundService", "addLink, known device: " + deviceId);
device.addLink(link); device.addLink(identityPackage, link);
} else { } else {
Log.i("BackgroundService", "addLink,unknown device: " + deviceId); Log.i("BackgroundService", "addLink,unknown device: " + deviceId);
String name = identityPackage.getString("deviceName"); String name = identityPackage.getString("deviceName");
device = new Device(getBaseContext(), deviceId, name, link); device = new Device(BackgroundService.this, identityPackage, link);
devices.put(deviceId, device); devices.put(deviceId, device);
device.addPairingCallback(devicePairingCallback); device.addPairingCallback(devicePairingCallback);
} }
@ -104,7 +104,7 @@ public class BackgroundService extends Service {
} }
@Override @Override
public void onConnectionLost(BaseComputerLink link) { public void onConnectionLost(BaseLink link) {
Device d = devices.get(link.getDeviceId()); Device d = devices.get(link.getDeviceId());
Log.i("onConnectionLost", "removeLink, deviceId: " + link.getDeviceId()); Log.i("onConnectionLost", "removeLink, deviceId: " + link.getDeviceId());
if (d != null) { if (d != null) {

View File

@ -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);
}
}

View File

@ -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;
}
}

View File

@ -15,7 +15,7 @@ import android.support.v4.app.NotificationCompat;
import android.util.Base64; import android.util.Base64;
import android.util.Log; 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.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.PairActivity; import org.kde.kdeconnect.UserInterface.PairActivity;
@ -34,7 +34,7 @@ import java.util.Set;
import java.util.Timer; import java.util.Timer;
import java.util.TimerTask; import java.util.TimerTask;
public class Device implements BaseComputerLink.PackageReceiver { public class Device implements BaseLink.PackageReceiver {
private Context context; private Context context;
@ -42,6 +42,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
private String name; private String name;
private PublicKey publicKey; private PublicKey publicKey;
private int notificationId; private int notificationId;
private int protocolVersion;
private enum PairStatus { private enum PairStatus {
NotPaired, NotPaired,
@ -61,7 +62,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
private ArrayList<PairingCallback> pairingCallback = new ArrayList<PairingCallback>(); private ArrayList<PairingCallback> pairingCallback = new ArrayList<PairingCallback>();
private Timer pairingTimer; private Timer pairingTimer;
private ArrayList<BaseComputerLink> links = new ArrayList<BaseComputerLink>(); private ArrayList<BaseLink> links = new ArrayList<BaseLink>();
private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>(); private HashMap<String, Plugin> plugins = new HashMap<String, Plugin>();
private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>(); private HashMap<String, Plugin> failedPlugins = new HashMap<String, Plugin>();
@ -77,6 +78,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
this.deviceId = deviceId; this.deviceId = deviceId;
this.name = settings.getString("deviceName", "unknown device"); this.name = settings.getString("deviceName", "unknown device");
this.pairStatus = PairStatus.Paired; this.pairStatus = PairStatus.Paired;
this.protocolVersion = NetworkPackage.ProtocolVersion; //We don't know it yet
try { try {
byte[] publicKeyBytes = Base64.decode(settings.getString("publicKey", ""), 0); 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 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); settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
//Log.e("Device","Constructor B"); //Log.e("Device","Constructor B");
this.context = context; this.context = context;
this.deviceId = deviceId; this.deviceId = np.getString("deviceId");
this.name = name; this.name = np.getString("deviceName");
this.protocolVersion = np.getInt("protocolVersion");
this.pairStatus = PairStatus.NotPaired; this.pairStatus = PairStatus.NotPaired;
this.publicKey = null; this.publicKey = null;
addLink(dl); addLink(np, dl);
} }
public String getName() { public String getName() {
@ -112,6 +115,10 @@ public class Device implements BaseComputerLink.PackageReceiver {
return deviceId; 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 //Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); 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;
}
@Override
public void sendSuccessful() {
pairingTimer = new Timer(); pairingTimer = new Timer();
pairingTimer.schedule(new TimerTask() { pairingTimer.schedule(new TimerTask() {
@Override @Override
public void run() { public void run() {
for (PairingCallback cb : pairingCallback) cb.pairingFailed(context.getString(R.string.error_timed_out)); for (PairingCallback cb : pairingCallback) {
cb.pairingFailed(context.getString(R.string.error_timed_out));
}
pairStatus = PairStatus.NotPaired; pairStatus = PairStatus.NotPaired;
} }
}, 20*1000); }, 20*1000);
pairStatus = PairStatus.Requested; 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;
}
});
} }
@ -207,9 +223,7 @@ public class Device implements BaseComputerLink.PackageReceiver {
//Send our own public key //Send our own public key
NetworkPackage np = NetworkPackage.createPublicKeyPackage(context); NetworkPackage np = NetworkPackage.createPublicKeyPackage(context);
boolean success = sendPackage(np); sendPackage(np); //TODO: Set a callback
if (!success) return;
pairStatus = PairStatus.Paired; pairStatus = PairStatus.Paired;
@ -255,15 +269,27 @@ public class Device implements BaseComputerLink.PackageReceiver {
return !links.isEmpty(); return !links.isEmpty();
} }
public void addLink(BaseComputerLink link) { public void addLink(NetworkPackage identityPackage, BaseLink link) {
this.protocolVersion = identityPackage.getInt("protocolVersion");
links.add(link); 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()); Log.i("Device","addLink "+link.getLinkProvider().getName()+" -> "+getName() + " active links: "+ links.size());
Collections.sort(links, new Comparator<BaseComputerLink>() { Collections.sort(links, new Comparator<BaseLink>() {
@Override @Override
public int compare(BaseComputerLink o, BaseComputerLink o2) { public int compare(BaseLink o, BaseLink o2) {
return o2.getLinkProvider().getPriority() - o.getLinkProvider().getPriority(); 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); link.removePackageReceiver(this);
links.remove(link); links.remove(link);
Log.i("Device","removeLink: "+link.getLinkProvider().getName() + " -> "+getName() + " active links: "+ links.size()); 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!"); Log.e("onPackageReceived","Device not paired, ignoring package!");
} else { } 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()) { for (Plugin plugin : plugins.values()) {
plugin.onPackageReceived(np); plugin.onPackageReceived(np);
@ -423,36 +432,46 @@ public class Device implements BaseComputerLink.PackageReceiver {
} }
public interface SendPackageFinishedCallback {
public boolean sendPackage(final NetworkPackage np) { void sendSuccessful();
void sendFailed();
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");
}
} }
new AsyncTask<Void,Void,Void>() { public void sendPackage(NetworkPackage np) {
sendPackage(np,null);
}
public void sendPackage(final NetworkPackage np, final SendPackageFinishedCallback callback) {
new Thread(new Runnable() {
@Override @Override
protected Void doInBackground(Void... voids) { public void run() {
for(BaseComputerLink link : links) {
//Log.e("sendPackage","Trying "+link.getLinkProvider().getName()); boolean useEncryption = (!np.getType().equals(NetworkPackage.PACKAGE_TYPE_PAIR) && isPaired());
if (link.sendPackage(np)) {
//Log.e("sent using", link.getLinkProvider().getName()); //Log.e("sendPackage", "Sending...");
return null;
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)"); 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();
} }

View File

@ -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";
}
}

View File

@ -11,6 +11,10 @@ import org.json.JSONArray;
import org.json.JSONException; import org.json.JSONException;
import org.json.JSONObject; 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.nio.charset.Charset;
import java.security.PrivateKey; import java.security.PrivateKey;
import java.security.PublicKey; import java.security.PublicKey;
@ -20,7 +24,7 @@ import javax.crypto.Cipher;
public class NetworkPackage { 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_IDENTITY = "kdeconnect.identity";
public final static String PACKAGE_TYPE_PAIR = "kdeconnect.pair"; 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_NOTIFICATION = "kdeconnect.notification";
public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard"; public final static String PACKAGE_TYPE_CLIPBOARD = "kdeconnect.clipboard";
public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris"; public final static String PACKAGE_TYPE_MPRIS = "kdeconnect.mpris";
public final static String PACKAGE_TYPE_SHARE = "kdeconnect.share";
private long mId; private long mId;
private String mType; private String mType;
private JSONObject mBody; private JSONObject mBody;
private InputStream mPayload;
private JSONObject mPayloadTransferInfo;
private int mPayloadSize;
private NetworkPackage() { private NetworkPackage() {
} }
public NetworkPackage(String type) { public NetworkPackage(String type) {
mId = System.currentTimeMillis(); mId = System.currentTimeMillis();
mType = type; mType = type;
mBody = new JSONObject(); mBody = new JSONObject();
mPayload = null;
mPayloadSize = 0;
mPayloadTransferInfo = new JSONObject();
} }
public String getType() { public String getType() {
@ -94,35 +106,60 @@ public class NetworkPackage {
public boolean has(String key) { return mBody.has(key); } public boolean has(String key) { return mBody.has(key); }
public boolean isEncrypted() { return mType.equals(PACKAGE_TYPE_ENCRYPTED); }
public String serialize() { public String serialize() {
JSONObject jo = new JSONObject(); JSONObject jo = new JSONObject();
try { try {
jo.put("id", mId); jo.put("id", mId);
jo.put("type", mType); jo.put("type", mType);
jo.put("body", mBody); jo.put("body", mBody);
} catch(Exception e) { 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. //QJSon does not escape slashes, but Java JSONObject does. Converting to QJson format.
String json = jo.toString().replace("\\/","/")+"\n"; String json = jo.toString().replace("\\/","/")+"\n";
if (!isEncrypted()) {
//Log.e("NetworkPackage.serialize", json); //Log.e("NetworkPackage.serialize", json);
}
return json; return json;
} }
static public NetworkPackage unserialize(String s) { static public NetworkPackage unserialize(String s) {
//Log.e("NetworkPackage.unserialize", s);
NetworkPackage np = new NetworkPackage(); NetworkPackage np = new NetworkPackage();
try { try {
JSONObject jo = new JSONObject(s); JSONObject jo = new JSONObject(s);
np.mId = jo.getLong("id"); np.mId = jo.getLong("id");
np.mType = jo.getString("type"); np.mType = jo.getString("type");
np.mBody = jo.getJSONObject("body"); 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) { } catch (Exception e) {
e.printStackTrace();
Log.e("NetworkPackage", "Unserialization exception unserializing "+s);
return null; return null;
} }
return np;
if (!np.isEncrypted()) {
//Log.e("NetworkPackage.unserialize", s);
} }
return np;
}
public void encrypt(PublicKey publicKey) throws Exception { public void encrypt(PublicKey publicKey) throws Exception {
@ -177,7 +214,6 @@ public class NetworkPackage {
return unserialize(decryptedJson); return unserialize(decryptedJson);
} }
static public NetworkPackage createIdentityPackage(Context context) { static public NetworkPackage createIdentityPackage(Context context) {
NetworkPackage np = new NetworkPackage(NetworkPackage.PACKAGE_TYPE_IDENTITY); 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;
}
} }

View File

@ -13,10 +13,10 @@ import android.widget.SeekBar;
import android.widget.Spinner; import android.widget.Spinner;
import android.widget.TextView; import android.widget.TextView;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.BackgroundService; import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.ComputerLinks.BaseComputerLink;
import org.kde.kdeconnect.Device; 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.NetworkPackage;
import org.kde.kdeconnect_tp.R; import org.kde.kdeconnect_tp.R;
@ -121,12 +121,12 @@ public class MprisActivity extends Activity {
BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() { BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
@Override @Override
public void onConnectionReceived(NetworkPackage identityPackage, BaseComputerLink link) { public void onConnectionReceived(NetworkPackage identityPackage, BaseLink link) {
connectToPlugin(); connectToPlugin();
} }
@Override @Override
public void onConnectionLost(BaseComputerLink link) { public void onConnectionLost(BaseLink link) {
} }
}; };

View File

@ -172,10 +172,10 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
@Override @Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) { 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(); Notification notification = statusBarNotification.getNotification();
NotificationId id = NotificationId.fromNotification(statusBarNotification); NotificationId id = NotificationId.fromNotification(statusBarNotification);
@ -185,20 +185,21 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
String packageName = statusBarNotification.getPackageName(); String packageName = statusBarNotification.getPackageName();
String appName = AppsHelper.appNameLookup(context, packageName); String appName = AppsHelper.appNameLookup(context, packageName);
//TODO: Add support for displaying app icons to desktop plasmoid and uncomment this piece of code
/*
try { try {
//TODO: Scale down app icon if too big and compress as JPG
Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName); Drawable drawableAppIcon = AppsHelper.appIconLookup(context, packageName);
Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon); Bitmap appIcon = ImagesHelper.drawableToBitmap(drawableAppIcon);
ByteArrayOutputStream outStream = new ByteArrayOutputStream(); ByteArrayOutputStream outStream = new ByteArrayOutputStream();
appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream); appIcon.compress(Bitmap.CompressFormat.PNG, 90, outStream);
byte[] bitmapData = outStream.toByteArray(); byte[] bitmapData = outStream.toByteArray();
byte[] serializedBitmapData = Base64.encode(bitmapData, Base64.NO_WRAP); np.setPayload(bitmapData);
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);
} catch(Exception e) { } catch(Exception e) {
e.printStackTrace(); e.printStackTrace();
Log.e("NotificationsPlugin","Error retrieving icon"); Log.e("NotificationsPlugin","Error retrieving icon");
} }
*/
np.set("id", id.serialize()); np.set("id", id.serialize());
np.set("appName", appName == null? packageName : appName); np.set("appName", appName == null? packageName : appName);
@ -224,7 +225,7 @@ public class NotificationsPlugin extends Plugin implements NotificationReceiver.
private void sendCurrentNotifications(NotificationReceiver service) { private void sendCurrentNotifications(NotificationReceiver service) {
StatusBarNotification[] notifications = service.getActiveNotifications(); StatusBarNotification[] notifications = service.getActiveNotifications();
for (StatusBarNotification notification : notifications) { for (StatusBarNotification notification : notifications) {
onNotificationPosted(notification, true); sendNotification(notification, true);
} }
} }

View File

@ -28,6 +28,17 @@ public class DeviceItem implements ListAdapter.Item {
TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title); TextView titleView = (TextView)v.findViewById(R.id.list_item_entry_title);
if (titleView != null) titleView.setText(device.getName()); 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() { v.setOnClickListener(new View.OnClickListener() {
@Override @Override

View File

@ -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;
}
}

View File

@ -13,6 +13,7 @@ public class SectionItem implements ListAdapter.Item {
public SectionItem(String title) { public SectionItem(String title) {
this.title = title; this.title = title;
this.isEmpty = false;
} }
@Override @Override

View File

@ -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<Device> devices = service.getDevices().values();
final ArrayList<Device> devicesList = new ArrayList<Device>();
final ArrayList<ListAdapter.Item> items = new ArrayList<ListAdapter.Item>();
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<Uri> 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<Uri>();
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<Uri> 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();
}
}

View File

@ -6,43 +6,27 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:minHeight="?android:attr/listPreferredItemHeight" android:minHeight="?android:attr/listPreferredItemHeight"
android:gravity="center_vertical" android:gravity="center_vertical"
android:paddingRight="?android:attr/scrollbarSize"> android:paddingRight="?android:attr/scrollbarSize"
<!-- android:orientation="vertical">
<ImageView
android:id="@+id/list_item_entry_drawable"
android:layout_width="wrap_content"
android:layout_height="fill_parent"
android:src="@android:drawable/ic_menu_preferences"
android:paddingLeft="9dp"/>
<RelativeLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="10dip"
android:layout_marginRight="6dip"
android:layout_marginTop="3dip"
android:layout_marginBottom="3dip"
android:layout_weight="0">
-->
<TextView android:id="@+id/list_item_entry_title" <TextView android:id="@+id/list_item_entry_title"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:singleLine="true" android:singleLine="true"
android:textAppearance="?android:attr/textAppearanceLarge" android:textAppearance="?android:attr/textAppearanceLarge"
android:ellipsize="marquee" android:ellipsize="marquee"
android:fadingEdge="horizontal" /> android:fadingEdge="horizontal"
android:text="" />
<!--
<TextView android:id="@+id/list_item_entry_summary" <TextView android:id="@+id/list_item_entry_summary"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/list_item_entry_title"
android:layout_alignLeft="@id/list_item_entry_title"
android:textAppearance="?android:attr/textAppearanceSmall" android:textAppearance="?android:attr/textAppearanceSmall"
android:singleLine="true" android:singleLine="true"
android:textColor="?android:attr/textColorSecondary" /> android:textColor="#CC2222"
android:visibility="gone"
android:text="" />
</RelativeLayout>
-->
</LinearLayout> </LinearLayout>

View File

@ -52,5 +52,9 @@
<string name="mpris_previous">Previous</string> <string name="mpris_previous">Previous</string>
<string name="mpris_next">Next</string> <string name="mpris_next">Next</string>
<string name="mpris_volume">Volume</string> <string name="mpris_volume">Volume</string>
<string name="share_to">Share To...</string>
<string name="protocol_version_older">This device uses an old protocol version</string>
<string name="protocol_version_newer">This device uses a newer protocol version</string>
</resources> </resources>

View File

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<module external.linked.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4"> <module external.linked.project.path="$MODULE_DIR$" external.root.project.path="$MODULE_DIR$" external.system.id="GRADLE" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true"> <component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output /> <exclude-output />
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">