mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Merge branch 'filetransfer'
This commit is contained in:
commit
f8f58ef2a2
@ -1,10 +1,11 @@
|
||||
<?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">
|
||||
<facet type="android" name="Android">
|
||||
<configuration>
|
||||
<option name="SELECTED_BUILD_VARIANT" value="Debug" />
|
||||
<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="SOURCE_GEN_TASK_NAME" value="generateDebugSources" />
|
||||
<option name="ALLOW_USER_CONFIGURATION" value="false" />
|
||||
@ -60,7 +61,6 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/bundles" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/classes" />
|
||||
<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/libs" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build/manifests" />
|
||||
@ -70,10 +70,10 @@
|
||||
<orderEntry type="jdk" jdkName="Android 4.3 Platform" jdkType="Android SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
<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="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>
|
||||
</module>
|
||||
|
||||
|
@ -117,6 +117,23 @@
|
||||
android:value="org.kde.kdeconnect.UserInterface.DeviceActivity" />
|
||||
</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"
|
||||
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
|
||||
<intent-filter>
|
||||
|
@ -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<PackageReceiver> receivers = new ArrayList<PackageReceiver>();
|
||||
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);
|
||||
|
||||
}
|
@ -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<ConnectionReceiver> connectionReceivers = new ArrayList<ConnectionReceiver>();
|
||||
|
||||
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);
|
@ -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);
|
||||
}
|
||||
}
|
@ -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<String, LanComputerLink> visibleComputers = new HashMap<String, LanComputerLink>();
|
||||
private HashMap<Long, LanComputerLink> nioSessions = new HashMap<Long, LanComputerLink>();
|
||||
private HashMap<String, LanLink> visibleComputers = new HashMap<String, LanLink>();
|
||||
private HashMap<Long, LanLink> nioSessions = new HashMap<Long, LanLink>();
|
||||
|
||||
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,33 +94,23 @@ 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 NioSocketConnector connector = new NioSocketConnector();
|
||||
@ -129,7 +123,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
);
|
||||
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));
|
||||
future.addListener(new IoFutureListener<IoFuture>() {
|
||||
@Override
|
||||
@ -138,7 +132,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
|
||||
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);
|
||||
@ -149,18 +143,17 @@ public class LanLinkProvider extends BaseLinkProvider {
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
Log.e("LanLinkProvider","Exception!!");
|
||||
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) {
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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";
|
||||
}
|
||||
}
|
@ -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<String> 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) {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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> pairingCallback = new ArrayList<PairingCallback>();
|
||||
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> failedPlugins = new HashMap<String, Plugin>();
|
||||
|
||||
@ -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);
|
||||
|
||||
if (!success) {
|
||||
for (PairingCallback cb : pairingCallback) cb.pairingFailed(res.getString(R.string.error_could_not_send_package));
|
||||
return;
|
||||
}
|
||||
sendPackage(np, new SendPackageFinishedCallback(){
|
||||
|
||||
@Override
|
||||
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));
|
||||
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;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
@ -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<BaseComputerLink>() {
|
||||
Collections.sort(links, new Comparator<BaseLink>() {
|
||||
@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 boolean sendPackage(final NetworkPackage np) {
|
||||
|
||||
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 interface SendPackageFinishedCallback {
|
||||
void sendSuccessful();
|
||||
void sendFailed();
|
||||
}
|
||||
|
||||
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
|
||||
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();
|
||||
}
|
||||
|
||||
|
||||
|
@ -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";
|
||||
}
|
||||
}
|
@ -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,35 +106,60 @@ 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);
|
||||
} 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.
|
||||
String json = jo.toString().replace("\\/","/")+"\n";
|
||||
|
||||
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;
|
||||
}
|
||||
return np;
|
||||
|
||||
if (!np.isEncrypted()) {
|
||||
//Log.e("NetworkPackage.unserialize", s);
|
||||
}
|
||||
|
||||
|
||||
return np;
|
||||
}
|
||||
|
||||
public void encrypt(PublicKey publicKey) throws Exception {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
};
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -13,6 +13,7 @@ public class SectionItem implements ListAdapter.Item {
|
||||
|
||||
public SectionItem(String title) {
|
||||
this.title = title;
|
||||
this.isEmpty = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -6,43 +6,27 @@
|
||||
android:layout_height="wrap_content"
|
||||
android:minHeight="?android:attr/listPreferredItemHeight"
|
||||
android:gravity="center_vertical"
|
||||
android:paddingRight="?android:attr/scrollbarSize">
|
||||
<!--
|
||||
<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"/>
|
||||
android:paddingRight="?android:attr/scrollbarSize"
|
||||
android:orientation="vertical">
|
||||
|
||||
|
||||
<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"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?android:attr/textAppearanceLarge"
|
||||
android:ellipsize="marquee"
|
||||
android:fadingEdge="horizontal" />
|
||||
android:fadingEdge="horizontal"
|
||||
android:text="" />
|
||||
|
||||
<!--
|
||||
<TextView android:id="@+id/list_item_entry_summary"
|
||||
android:layout_width="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:singleLine="true"
|
||||
android:textColor="?android:attr/textColorSecondary" />
|
||||
android:textColor="#CC2222"
|
||||
android:visibility="gone"
|
||||
android:text="" />
|
||||
|
||||
|
||||
</RelativeLayout>
|
||||
-->
|
||||
</LinearLayout>
|
||||
|
@ -52,5 +52,9 @@
|
||||
<string name="mpris_previous">Previous</string>
|
||||
<string name="mpris_next">Next</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>
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?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">
|
||||
<exclude-output />
|
||||
<content url="file://$MODULE_DIR$">
|
||||
|
Loading…
x
Reference in New Issue
Block a user