mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 18:07:55 +00:00
Add DeviceInfo class
`DeviceInfo` contains all the properties we need to instantiate a `Device`: id, name, type, cert, capabilities and protocol version. Before, we had a mix of passing those around as arguments or passing identity packets (ie: json). This simplifies the `Device` class quite a bit. Now, `BaseLink` subclasses need to implement the `getDeviceInfo()` interface that returns a `DeviceInfo`, which is what we will pass around and eventually use to instantiate a `Device`. This means that identity packets are an implementation detail of the `LanLinkProvider` and that other implementations could get the `DeviceInfo` in a different way. In a future, we can add a mechanism for links to notify when their `DeviceInfo` changed. This will allow us to better support device renames (which now are implemented by reconnecting to the device and don't always work) or sending the capabilities fields later in the case of MacOS/iOS where we can't send them in a UDP packet due to the size limit of 1500 bytes.
This commit is contained in:
parent
310e61b570
commit
8c1603f6e4
@ -12,6 +12,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.WorkerThread;
|
import androidx.annotation.WorkerThread;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -26,20 +27,20 @@ public abstract class BaseLink {
|
|||||||
|
|
||||||
protected final Context context;
|
protected final Context context;
|
||||||
private final BaseLinkProvider linkProvider;
|
private final BaseLinkProvider linkProvider;
|
||||||
private final String deviceId;
|
|
||||||
private final ArrayList<PacketReceiver> receivers = new ArrayList<>();
|
private final ArrayList<PacketReceiver> receivers = new ArrayList<>();
|
||||||
|
|
||||||
protected BaseLink(@NonNull Context context, @NonNull String deviceId, @NonNull BaseLinkProvider linkProvider) {
|
protected BaseLink(@NonNull Context context, @NonNull BaseLinkProvider linkProvider) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
this.linkProvider = linkProvider;
|
this.linkProvider = linkProvider;
|
||||||
this.deviceId = deviceId;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/* To be implemented by each link for pairing handlers */
|
/* To be implemented by each link for pairing handlers */
|
||||||
public abstract String getName();
|
public abstract String getName();
|
||||||
|
|
||||||
|
public abstract DeviceInfo getDeviceInfo();
|
||||||
|
|
||||||
public String getDeviceId() {
|
public String getDeviceId() {
|
||||||
return deviceId;
|
return getDeviceInfo().id;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BaseLinkProvider getLinkProvider() {
|
public BaseLinkProvider getLinkProvider() {
|
||||||
@ -54,7 +55,7 @@ public abstract class BaseLink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
//Should be called from a background thread listening for packets
|
//Should be called from a background thread listening for packets
|
||||||
protected void packetReceived(@NonNull NetworkPacket np) {
|
public void packetReceived(@NonNull NetworkPacket np) {
|
||||||
for(PacketReceiver pr : receivers) {
|
for(PacketReceiver pr : receivers) {
|
||||||
pr.onPacketReceived(np);
|
pr.onPacketReceived(np);
|
||||||
}
|
}
|
||||||
|
@ -8,23 +8,17 @@ package org.kde.kdeconnect.Backends;
|
|||||||
|
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
|
||||||
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
|
|
||||||
public abstract class BaseLinkProvider {
|
public abstract class BaseLinkProvider {
|
||||||
|
|
||||||
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
|
|
||||||
|
|
||||||
public interface ConnectionReceiver {
|
public interface ConnectionReceiver {
|
||||||
void onConnectionReceived(@NonNull final String deviceId,
|
void onConnectionReceived(@NonNull final BaseLink link);
|
||||||
@NonNull final Certificate certificate,
|
|
||||||
@NonNull final NetworkPacket identityPacket,
|
|
||||||
@NonNull final BaseLink link);
|
|
||||||
void onConnectionLost(BaseLink link);
|
void onConnectionLost(BaseLink link);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
|
||||||
|
|
||||||
public void addConnectionReceiver(ConnectionReceiver cr) {
|
public void addConnectionReceiver(ConnectionReceiver cr) {
|
||||||
connectionReceivers.add(cr);
|
connectionReceivers.add(cr);
|
||||||
}
|
}
|
||||||
@ -36,13 +30,10 @@ public abstract class BaseLinkProvider {
|
|||||||
/**
|
/**
|
||||||
* To be called from the child classes when a link to a new device is established
|
* To be called from the child classes when a link to a new device is established
|
||||||
*/
|
*/
|
||||||
protected void onConnectionReceived(@NonNull final String deviceId,
|
protected void onConnectionReceived(@NonNull final BaseLink link) {
|
||||||
@NonNull final Certificate certificate,
|
|
||||||
@NonNull final NetworkPacket identityPacket,
|
|
||||||
@NonNull final BaseLink link) {
|
|
||||||
//Log.i("KDE/LinkProvider", "onConnectionReceived");
|
//Log.i("KDE/LinkProvider", "onConnectionReceived");
|
||||||
for(ConnectionReceiver cr : connectionReceivers) {
|
for(ConnectionReceiver cr : connectionReceivers) {
|
||||||
cr.onConnectionReceived(deviceId, certificate, identityPacket, link);
|
cr.onConnectionReceived(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import org.json.JSONException;
|
|||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -34,6 +35,7 @@ public class BluetoothLink extends BaseLink {
|
|||||||
private final OutputStream output;
|
private final OutputStream output;
|
||||||
private final BluetoothDevice remoteAddress;
|
private final BluetoothDevice remoteAddress;
|
||||||
private final BluetoothLinkProvider linkProvider;
|
private final BluetoothLinkProvider linkProvider;
|
||||||
|
private final DeviceInfo deviceInfo;
|
||||||
|
|
||||||
private boolean continueAccepting = true;
|
private boolean continueAccepting = true;
|
||||||
|
|
||||||
@ -93,11 +95,12 @@ public class BluetoothLink extends BaseLink {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
public BluetoothLink(Context context, ConnectionMultiplexer connection, InputStream input, OutputStream output, BluetoothDevice remoteAddress, String deviceId, BluetoothLinkProvider linkProvider) {
|
public BluetoothLink(Context context, ConnectionMultiplexer connection, InputStream input, OutputStream output, BluetoothDevice remoteAddress, DeviceInfo deviceInfo, BluetoothLinkProvider linkProvider) {
|
||||||
super(context, deviceId, linkProvider);
|
super(context, linkProvider);
|
||||||
this.connection = connection;
|
this.connection = connection;
|
||||||
this.input = input;
|
this.input = input;
|
||||||
this.output = output;
|
this.output = output;
|
||||||
|
this.deviceInfo = deviceInfo;
|
||||||
this.remoteAddress = remoteAddress;
|
this.remoteAddress = remoteAddress;
|
||||||
this.linkProvider = linkProvider;
|
this.linkProvider = linkProvider;
|
||||||
}
|
}
|
||||||
@ -111,6 +114,11 @@ public class BluetoothLink extends BaseLink {
|
|||||||
return "BluetoothLink";
|
return "BluetoothLink";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceInfo getDeviceInfo() {
|
||||||
|
return deviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
if (connection == null) {
|
if (connection == null) {
|
||||||
return;
|
return;
|
||||||
@ -120,7 +128,7 @@ public class BluetoothLink extends BaseLink {
|
|||||||
connection.close();
|
connection.close();
|
||||||
} catch (IOException ignored) {
|
} catch (IOException ignored) {
|
||||||
}
|
}
|
||||||
linkProvider.disconnectedLink(this, getDeviceId(), remoteAddress);
|
linkProvider.disconnectedLink(this, remoteAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessage(NetworkPacket np) throws JSONException, IOException {
|
private void sendMessage(NetworkPacket np) throws JSONException, IOException {
|
||||||
|
@ -20,6 +20,8 @@ import android.util.Log;
|
|||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
@ -54,10 +56,6 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
|
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
|
||||||
String deviceId = identityPacket.getString("deviceId");
|
String deviceId = identityPacket.getString("deviceId");
|
||||||
String certificateString = identityPacket.getString("certificate");
|
|
||||||
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
|
||||||
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
|
||||||
|
|
||||||
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
|
Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
|
||||||
BluetoothLink oldLink = visibleDevices.get(deviceId);
|
BluetoothLink oldLink = visibleDevices.get(deviceId);
|
||||||
if (oldLink == link) {
|
if (oldLink == link) {
|
||||||
@ -65,8 +63,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
visibleDevices.put(deviceId, link);
|
visibleDevices.put(deviceId, link);
|
||||||
onConnectionReceived(deviceId, certificate, identityPacket, link);
|
onConnectionReceived(link);
|
||||||
link.startListening();
|
link.startListening();
|
||||||
|
link.packetReceived(identityPacket);
|
||||||
if (oldLink != null) {
|
if (oldLink != null) {
|
||||||
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
|
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
|
||||||
oldLink.disconnect();
|
oldLink.disconnect();
|
||||||
@ -127,9 +126,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
return "BluetoothLinkProvider";
|
return "BluetoothLinkProvider";
|
||||||
}
|
}
|
||||||
|
|
||||||
public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothDevice remoteAddress) {
|
public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) {
|
||||||
sockets.remove(remoteAddress);
|
sockets.remove(remoteAddress);
|
||||||
visibleDevices.remove(deviceId);
|
visibleDevices.remove(link.getDeviceId());
|
||||||
onConnectionLost(link);
|
onConnectionLost(link);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,8 +195,10 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
OutputStream outputStream = connection.getDefaultOutputStream();
|
OutputStream outputStream = connection.getDefaultOutputStream();
|
||||||
InputStream inputStream = connection.getDefaultInputStream();
|
InputStream inputStream = connection.getDefaultInputStream();
|
||||||
|
|
||||||
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||||
|
NetworkPacket np = myDeviceInfo.toIdentityPacket();
|
||||||
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
||||||
|
|
||||||
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
|
byte[] message = np.serialize().getBytes(Charsets.UTF_8);
|
||||||
outputStream.write(message);
|
outputStream.write(message);
|
||||||
outputStream.flush();
|
outputStream.flush();
|
||||||
@ -223,9 +224,15 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
Log.i("BTLinkProvider/Server", "Received identity packet");
|
Log.i("BTLinkProvider/Server", "Received identity packet");
|
||||||
|
|
||||||
|
String certificateString = identityPacket.getString("certificate");
|
||||||
|
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
||||||
|
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
||||||
|
|
||||||
|
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
||||||
|
|
||||||
BluetoothLink link = new BluetoothLink(context, connection,
|
BluetoothLink link = new BluetoothLink(context, connection,
|
||||||
inputStream, outputStream, socket.getRemoteDevice(),
|
inputStream, outputStream, socket.getRemoteDevice(),
|
||||||
identityPacket.getString("deviceId"), BluetoothLinkProvider.this);
|
deviceInfo, BluetoothLinkProvider.this);
|
||||||
addLink(identityPacket, link);
|
addLink(identityPacket, link);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
synchronized (sockets) {
|
synchronized (sockets) {
|
||||||
@ -360,7 +367,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
Log.i("BTLinkProvider/Client", "Received identity packet");
|
Log.i("BTLinkProvider/Client", "Received identity packet");
|
||||||
|
|
||||||
String myId = NetworkPacket.createIdentityPacket(context).getString("deviceId");
|
String myId = DeviceHelper.getDeviceId(context);
|
||||||
if (identityPacket.getString("deviceId").equals(myId)) {
|
if (identityPacket.getString("deviceId").equals(myId)) {
|
||||||
// Probably won't happen, but just to be safe
|
// Probably won't happen, but just to be safe
|
||||||
connection.close();
|
connection.close();
|
||||||
@ -373,10 +380,18 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
Log.i("BTLinkProvider/Client", "identity packet received, creating link");
|
Log.i("BTLinkProvider/Client", "identity packet received, creating link");
|
||||||
|
|
||||||
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
|
String certificateString = identityPacket.getString("certificate");
|
||||||
socket.getRemoteDevice(), identityPacket.getString("deviceId"), BluetoothLinkProvider.this);
|
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
||||||
|
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
||||||
|
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
||||||
|
|
||||||
|
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
|
||||||
|
socket.getRemoteDevice(), deviceInfo, BluetoothLinkProvider.this);
|
||||||
|
|
||||||
|
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||||
|
NetworkPacket np2 = myDeviceInfo.toIdentityPacket();
|
||||||
|
np2.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
|
||||||
|
|
||||||
NetworkPacket np2 = NetworkPacket.createIdentityPacket(context);
|
|
||||||
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
|
link.sendPacket(np2, new Device.SendPacketStatusCallback() {
|
||||||
@Override
|
@Override
|
||||||
public void onSuccess() {
|
public void onSuccess() {
|
||||||
|
@ -16,6 +16,7 @@ import org.json.JSONObject;
|
|||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
@ -42,6 +43,8 @@ public class LanLink extends BaseLink {
|
|||||||
Locally, Remotely
|
Locally, Remotely
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final DeviceInfo deviceInfo;
|
||||||
|
|
||||||
private volatile SSLSocket socket = null;
|
private volatile SSLSocket socket = null;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -99,8 +102,9 @@ public class LanLink extends BaseLink {
|
|||||||
return oldSocket;
|
return oldSocket;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LanLink(Context context, String deviceId, BaseLinkProvider linkProvider, SSLSocket socket) throws IOException {
|
public LanLink(@NonNull Context context, @NonNull DeviceInfo deviceInfo, @NonNull BaseLinkProvider linkProvider, @NonNull SSLSocket socket) throws IOException {
|
||||||
super(context, deviceId, linkProvider);
|
super(context, linkProvider);
|
||||||
|
this.deviceInfo = deviceInfo;
|
||||||
reset(socket);
|
reset(socket);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,6 +113,11 @@ public class LanLink extends BaseLink {
|
|||||||
return "LanLink";
|
return "LanLink";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceInfo getDeviceInfo() {
|
||||||
|
return deviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
@WorkerThread
|
@WorkerThread
|
||||||
@Override
|
@Override
|
||||||
public boolean sendPacket(@NonNull NetworkPacket np, @NonNull final Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) {
|
public boolean sendPacket(@NonNull NetworkPacket np, @NonNull final Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) {
|
||||||
|
@ -17,6 +17,7 @@ import org.json.JSONException;
|
|||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
import org.kde.kdeconnect.Helpers.ThreadHelper;
|
||||||
@ -50,7 +51,7 @@ import kotlin.text.Charsets;
|
|||||||
/**
|
/**
|
||||||
* This LanLinkProvider creates {@link LanLink}s to other devices on the same
|
* This LanLinkProvider creates {@link LanLink}s to other devices on the same
|
||||||
* WiFi network. The first packet sent over a socket must be an
|
* WiFi network. The first packet sent over a socket must be an
|
||||||
* {@link NetworkPacket#createIdentityPacket(Context)}.
|
* {@link DeviceInfo#toIdentityPacket()}.
|
||||||
*
|
*
|
||||||
* @see #identityPacketReceived(NetworkPacket, Socket, LanLink.ConnectionStarted)
|
* @see #identityPacketReceived(NetworkPacket, Socket, LanLink.ConnectionStarted)
|
||||||
*/
|
*/
|
||||||
@ -128,13 +129,19 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
Log.i("KDE/LanLinkProvider", "Broadcast identity packet received from " + identityPacket.getString("deviceName"));
|
Log.i("KDE/LanLinkProvider", "Broadcast identity packet received from " + identityPacket.getString("deviceName"));
|
||||||
|
|
||||||
int tcpPort = identityPacket.getInt("tcpPort", MIN_PORT);
|
int tcpPort = identityPacket.getInt("tcpPort", MIN_PORT);
|
||||||
|
if (tcpPort < MIN_PORT || tcpPort > MAX_PORT) {
|
||||||
|
Log.e("LanLinkProvider", "TCP port outside of kdeconnect's range");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
SocketFactory socketFactory = SocketFactory.getDefault();
|
SocketFactory socketFactory = SocketFactory.getDefault();
|
||||||
Socket socket = socketFactory.createSocket(address, tcpPort);
|
Socket socket = socketFactory.createSocket(address, tcpPort);
|
||||||
configureSocket(socket);
|
configureSocket(socket);
|
||||||
|
|
||||||
|
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||||
|
NetworkPacket myIdentity = myDeviceInfo.toIdentityPacket();
|
||||||
|
|
||||||
OutputStream out = socket.getOutputStream();
|
OutputStream out = socket.getOutputStream();
|
||||||
NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context);
|
|
||||||
out.write(myIdentity.serialize().getBytes());
|
out.write(myIdentity.serialize().getBytes());
|
||||||
out.flush();
|
out.flush();
|
||||||
|
|
||||||
@ -190,17 +197,19 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
identityPacketReceived(identityPacket, socket, connectionStarted);
|
identityPacketReceived(identityPacket, socket, connectionStarted);
|
||||||
}
|
}
|
||||||
|
|
||||||
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + identityPacket.getString("deviceName") + " trusted:" + isDeviceTrusted);
|
String deviceName = identityPacket.getString("deviceName", "unknown");
|
||||||
|
Log.i("KDE/LanLinkProvider", "Starting SSL handshake with " + deviceName + " trusted:" + isDeviceTrusted);
|
||||||
|
|
||||||
final SSLSocket sslSocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
final SSLSocket sslSocket = SslHelper.convertToSslSocket(context, socket, deviceId, isDeviceTrusted, clientMode);
|
||||||
sslSocket.addHandshakeCompletedListener(event -> {
|
sslSocket.addHandshakeCompletedListener(event -> {
|
||||||
String mode = clientMode ? "client" : "server";
|
String mode = clientMode ? "client" : "server";
|
||||||
try {
|
try {
|
||||||
Certificate certificate = event.getPeerCertificates()[0];
|
Certificate certificate = event.getPeerCertificates()[0];
|
||||||
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
|
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
|
||||||
addLink(deviceId, certificate, identityPacket, sslSocket);
|
Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + deviceName + " secured with " + event.getCipherSuite());
|
||||||
} catch (Exception e) {
|
addLink(sslSocket, deviceInfo);
|
||||||
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
|
} catch (IOException e) {
|
||||||
|
Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + deviceName, e);
|
||||||
Device device = KdeConnect.getInstance().getDevice(deviceId);
|
Device device = KdeConnect.getInstance().getDevice(deviceId);
|
||||||
if (device == null) {
|
if (device == null) {
|
||||||
return;
|
return;
|
||||||
@ -218,24 +227,26 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
/**
|
/**
|
||||||
* Add or update a link in the {@link #visibleDevices} map.
|
* Add or update a link in the {@link #visibleDevices} map.
|
||||||
*
|
*
|
||||||
* @param deviceId remote device id
|
|
||||||
* @param certificate remote device certificate
|
|
||||||
* @param identityPacket identity packet with the remote device's device name, type, protocol version, etc.
|
|
||||||
* @param socket a new Socket, which should be used to send and receive packets from the remote device
|
* @param socket a new Socket, which should be used to send and receive packets from the remote device
|
||||||
|
* @param deviceInfo remote device info
|
||||||
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket)}
|
* @throws IOException if an exception is thrown by {@link LanLink#reset(SSLSocket)}
|
||||||
*/
|
*/
|
||||||
private void addLink(String deviceId, Certificate certificate, final NetworkPacket identityPacket, SSLSocket socket) throws IOException {
|
private void addLink(SSLSocket socket, DeviceInfo deviceInfo) throws IOException {
|
||||||
LanLink currentLink = visibleDevices.get(deviceId);
|
LanLink link = visibleDevices.get(deviceInfo.id);
|
||||||
if (currentLink != null) {
|
if (link != null) {
|
||||||
//Update old link
|
if (!link.getDeviceInfo().certificate.equals(deviceInfo.certificate)) {
|
||||||
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
|
Log.e("LanLinkProvider", "LanLink was asked to replace a socket but the certificate doesn't match, aborting");
|
||||||
final Socket oldSocket = currentLink.reset(socket);
|
return;
|
||||||
|
}
|
||||||
|
// Update existing link
|
||||||
|
Log.d("KDE/LanLinkProvider", "Reusing same link for device " + deviceInfo.id);
|
||||||
|
final Socket oldSocket = link.reset(socket);
|
||||||
} else {
|
} else {
|
||||||
Log.i("KDE/LanLinkProvider", "Creating a new link for device " + deviceId);
|
// Create a new link
|
||||||
//Let's create the link
|
Log.d("KDE/LanLinkProvider", "Creating a new link for device " + deviceInfo.id);
|
||||||
LanLink link = new LanLink(context, deviceId, this, socket);
|
link = new LanLink(context, deviceInfo, this, socket);
|
||||||
visibleDevices.put(deviceId, link);
|
visibleDevices.put(deviceInfo.id, link);
|
||||||
onConnectionReceived(deviceId, certificate, identityPacket, link);
|
onConnectionReceived(link);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,7 +379,8 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
NetworkPacket identity = NetworkPacket.createIdentityPacket(context);
|
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
|
||||||
|
NetworkPacket identity = myDeviceInfo.toIdentityPacket();
|
||||||
identity.set("tcpPort", tcpServer.getLocalPort());
|
identity.set("tcpPort", tcpServer.getLocalPort());
|
||||||
|
|
||||||
byte[] bytes;
|
byte[] bytes;
|
||||||
|
@ -14,12 +14,14 @@ import androidx.annotation.WorkerThread;
|
|||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.Device;
|
||||||
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
|
|
||||||
public class LoopbackLink extends BaseLink {
|
public class LoopbackLink extends BaseLink {
|
||||||
|
|
||||||
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
|
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
|
||||||
super(context, "loopback", linkProvider);
|
super(context, linkProvider);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -40,4 +42,9 @@ public class LoopbackLink extends BaseLink {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public DeviceInfo getDeviceInfo() {
|
||||||
|
return DeviceHelper.getDeviceInfo(context);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,9 +9,6 @@ package org.kde.kdeconnect.Backends.LoopbackBackend;
|
|||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
import org.kde.kdeconnect.Backends.BaseLinkProvider;
|
||||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
|
||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
|
||||||
|
|
||||||
public class LoopbackLinkProvider extends BaseLinkProvider {
|
public class LoopbackLinkProvider extends BaseLinkProvider {
|
||||||
|
|
||||||
@ -32,9 +29,8 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNetworkChange() {
|
public void onNetworkChange() {
|
||||||
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
LoopbackLink link = new LoopbackLink(context, this);
|
||||||
String deviceId = DeviceHelper.getDeviceId(context);
|
onConnectionReceived(link);
|
||||||
onConnectionReceived(deviceId, SslHelper.certificate, np, new LoopbackLink(context, this));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -14,7 +14,6 @@ import android.content.Intent;
|
|||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
import android.util.Base64;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import androidx.annotation.AnyThread;
|
import androidx.annotation.AnyThread;
|
||||||
@ -26,7 +25,6 @@ import androidx.core.content.ContextCompat;
|
|||||||
|
|
||||||
import org.apache.commons.collections4.MultiValuedMap;
|
import org.apache.commons.collections4.MultiValuedMap;
|
||||||
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
|
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.kde.kdeconnect.Backends.BaseLink;
|
import org.kde.kdeconnect.Backends.BaseLink;
|
||||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
import org.kde.kdeconnect.Helpers.NotificationHelper;
|
||||||
@ -38,13 +36,8 @@ import org.kde.kdeconnect_tp.R;
|
|||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.cert.Certificate;
|
import java.security.cert.Certificate;
|
||||||
import java.security.cert.CertificateEncodingException;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArrayList;
|
import java.util.concurrent.CopyOnWriteArrayList;
|
||||||
@ -53,30 +46,26 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private final String deviceId;
|
final DeviceInfo deviceInfo;
|
||||||
private String name;
|
|
||||||
public Certificate certificate;
|
|
||||||
private int notificationId;
|
private int notificationId;
|
||||||
private int protocolVersion;
|
|
||||||
private DeviceType deviceType;
|
|
||||||
PairingHandler pairingHandler;
|
PairingHandler pairingHandler;
|
||||||
private final CopyOnWriteArrayList<PairingHandler.PairingCallback> pairingCallbacks = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<PairingHandler.PairingCallback> pairingCallbacks = new CopyOnWriteArrayList<>();
|
||||||
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
|
||||||
private DevicePacketQueue packetQueue;
|
private DevicePacketQueue packetQueue;
|
||||||
private List<String> supportedPlugins = new ArrayList<>();
|
private List<String> supportedPlugins;
|
||||||
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Plugin> plugins = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Plugin> pluginsWithoutPermissions = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Plugin> pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>();
|
||||||
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
||||||
private final SharedPreferences settings;
|
private final SharedPreferences settings;
|
||||||
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
|
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
|
||||||
private Set<String> incomingCapabilities = new HashSet<>();
|
|
||||||
|
|
||||||
public boolean supportsPacketType(String type) {
|
public boolean supportsPacketType(String type) {
|
||||||
if (incomingCapabilities == null) {
|
if (deviceInfo.incomingCapabilities == null) {
|
||||||
return true;
|
return true;
|
||||||
} else {
|
} else {
|
||||||
return incomingCapabilities.contains(type);
|
return deviceInfo.incomingCapabilities.contains(type);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,109 +73,52 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
void onPluginsChanged(@NonNull Device device);
|
void onPluginsChanged(@NonNull Device device);
|
||||||
}
|
}
|
||||||
|
|
||||||
public enum DeviceType {
|
/**
|
||||||
Phone,
|
* Constructor for remembered, already-trusted devices.
|
||||||
Tablet,
|
* Given the deviceId, it will load the other properties from SharedPreferences.
|
||||||
Computer,
|
*/
|
||||||
Tv;
|
Device(@NonNull Context context, @NonNull String deviceId) {
|
||||||
|
|
||||||
static public DeviceType FromString(String s) {
|
|
||||||
if ("tablet".equals(s)) return Tablet;
|
|
||||||
if ("phone".equals(s)) return Phone;
|
|
||||||
if ("tv".equals(s)) return Tv;
|
|
||||||
return Computer; //Default
|
|
||||||
}
|
|
||||||
|
|
||||||
@NonNull
|
|
||||||
public String toString() {
|
|
||||||
switch (this) {
|
|
||||||
case Tablet:
|
|
||||||
return "tablet";
|
|
||||||
case Phone:
|
|
||||||
return "phone";
|
|
||||||
case Tv:
|
|
||||||
return "tv";
|
|
||||||
default:
|
|
||||||
return "desktop";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public Drawable getIcon(Context context) {
|
|
||||||
int drawableId;
|
|
||||||
switch (this) {
|
|
||||||
case Phone:
|
|
||||||
drawableId = R.drawable.ic_device_phone_32dp;
|
|
||||||
break;
|
|
||||||
case Tablet:
|
|
||||||
drawableId = R.drawable.ic_device_tablet_32dp;
|
|
||||||
break;
|
|
||||||
case Tv:
|
|
||||||
drawableId = R.drawable.ic_device_tv_32dp;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
drawableId = R.drawable.ic_device_laptop_32dp;
|
|
||||||
}
|
|
||||||
return ContextCompat.getDrawable(context, drawableId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remembered trusted device, we need to wait for a incoming Link to communicate
|
|
||||||
Device(@NonNull Context context, @NonNull String deviceId) throws CertificateException {
|
|
||||||
|
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
|
||||||
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
||||||
|
this.deviceInfo = DeviceInfo.loadFromSettings(context, deviceId, settings);
|
||||||
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
|
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
|
||||||
|
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
|
||||||
this.deviceId = deviceId;
|
Log.i("Device","Loading trusted device: " + deviceInfo.name);
|
||||||
this.name = settings.getString("deviceName", context.getString(R.string.unknown_device));
|
|
||||||
this.protocolVersion = 0; //We don't know it yet
|
|
||||||
this.deviceType = DeviceType.FromString(settings.getString("deviceType", "desktop"));
|
|
||||||
this.certificate = SslHelper.getDeviceCertificate(context, deviceId);
|
|
||||||
|
|
||||||
Log.i("Device","Loading trusted device: " + this.name);
|
|
||||||
|
|
||||||
//Assume every plugin is supported until addLink is called and we can get the actual list
|
|
||||||
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
|
||||||
|
|
||||||
//Do not load plugins yet, the device is not present
|
|
||||||
//reloadPluginsFromSettings();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Device known via an incoming connection sent to us via a Link, we don't trust it yet
|
/**
|
||||||
Device(@NonNull Context context, @NonNull String deviceId, @NonNull Certificate certificate, @NonNull NetworkPacket identityPacket, @NonNull BaseLink dl) {
|
* Constructor for devices discovered but not trusted yet.
|
||||||
Log.i("Device","Creating untrusted device");
|
* Gets the DeviceInfo by calling link.getDeviceInfo() on the link passed.
|
||||||
|
* This constructor also calls addLink() with the link you pass to it, since it's not legal to have an unpaired Device with 0 links.
|
||||||
|
*/
|
||||||
|
Device(@NonNull Context context, @NonNull BaseLink link) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.deviceInfo = link.getDeviceInfo();
|
||||||
this.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
|
||||||
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
|
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
|
||||||
|
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
|
||||||
this.deviceId = deviceId;
|
Log.i("Device","Creating untrusted device: "+ deviceInfo.name);
|
||||||
this.certificate = certificate;
|
addLink(link);
|
||||||
|
|
||||||
// The following properties are read from the identityPacket in addLink since they can change in future identity packets
|
|
||||||
this.name = context.getString(R.string.unknown_device);
|
|
||||||
this.deviceType = DeviceType.Computer;
|
|
||||||
this.protocolVersion = 0;
|
|
||||||
|
|
||||||
addLink(identityPacket, dl);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
return StringUtils.defaultString(name, context.getString(R.string.unknown_device));
|
return deviceInfo.name;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Drawable getIcon() {
|
public Drawable getIcon() {
|
||||||
return deviceType.getIcon(context);
|
return deviceInfo.type.getIcon(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
public DeviceType getDeviceType() {
|
public DeviceType getDeviceType() {
|
||||||
return deviceType;
|
return deviceInfo.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getDeviceId() {
|
public String getDeviceId() {
|
||||||
return deviceId;
|
return deviceInfo.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Certificate getCertificate() {
|
||||||
|
return deviceInfo.certificate;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Context getContext() {
|
public Context getContext() {
|
||||||
@ -195,7 +127,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
|
|
||||||
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
|
//Returns 0 if the version matches, < 0 if it is older or > 0 if it is newer
|
||||||
public int compareProtocolVersion() {
|
public int compareProtocolVersion() {
|
||||||
return protocolVersion - DeviceHelper.ProtocolVersion;
|
return deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -257,20 +189,11 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
hidePairingNotification();
|
hidePairingNotification();
|
||||||
|
|
||||||
// Store current device certificate so we can check it in the future (TOFU)
|
// Store current device certificate so we can check it in the future (TOFU)
|
||||||
SharedPreferences.Editor editor = context.getSharedPreferences(getDeviceId(), Context.MODE_PRIVATE).edit();
|
deviceInfo.saveInSettings(Device.this.settings);
|
||||||
try {
|
|
||||||
String encodedCertificate = Base64.encodeToString(certificate.getEncoded(), 0);
|
|
||||||
editor.putString("certificate", encodedCertificate);
|
|
||||||
} catch(CertificateEncodingException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
editor.putString("deviceName", name);
|
|
||||||
editor.putString("deviceType", deviceType.toString());
|
|
||||||
editor.apply();
|
|
||||||
|
|
||||||
// Store as trusted device
|
// Store as trusted device
|
||||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||||
preferences.edit().putBoolean(deviceId, true).apply();
|
preferences.edit().putBoolean(deviceInfo.id, true).apply();
|
||||||
|
|
||||||
reloadPluginsFromSettings();
|
reloadPluginsFromSettings();
|
||||||
|
|
||||||
@ -290,9 +213,9 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
@Override
|
@Override
|
||||||
public void unpaired() {
|
public void unpaired() {
|
||||||
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
|
||||||
preferences.edit().remove(deviceId).apply();
|
preferences.edit().remove(deviceInfo.id).apply();
|
||||||
|
|
||||||
SharedPreferences devicePreferences = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
|
SharedPreferences devicePreferences = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
|
||||||
devicePreferences.edit().clear().apply();
|
devicePreferences.edit().clear().apply();
|
||||||
|
|
||||||
for (PairingHandler.PairingCallback cb : pairingCallbacks) {
|
for (PairingHandler.PairingCallback cb : pairingCallbacks) {
|
||||||
@ -334,7 +257,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
|
|
||||||
final NotificationManager notificationManager = ContextCompat.getSystemService(getContext(), NotificationManager.class);
|
final NotificationManager notificationManager = ContextCompat.getSystemService(getContext(), NotificationManager.class);
|
||||||
|
|
||||||
String verificationKeyShort = SslHelper.getVerificationKey(SslHelper.certificate, certificate).substring(8);
|
String verificationKeyShort = SslHelper.getVerificationKey(SslHelper.certificate, deviceInfo.certificate).substring(8);
|
||||||
|
|
||||||
Notification noti = new NotificationCompat.Builder(getContext(), NotificationHelper.Channels.DEFAULT)
|
Notification noti = new NotificationCompat.Builder(getContext(), NotificationHelper.Channels.DEFAULT)
|
||||||
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
|
.setContentTitle(res.getString(R.string.pairing_request_from, getName()))
|
||||||
@ -365,7 +288,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
return !links.isEmpty();
|
return !links.isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addLink(NetworkPacket identityPacket, BaseLink link) {
|
public void addLink(BaseLink link) {
|
||||||
if (links.isEmpty()) {
|
if (links.isEmpty()) {
|
||||||
packetQueue = new DevicePacketQueue(this);
|
packetQueue = new DevicePacketQueue(this);
|
||||||
}
|
}
|
||||||
@ -373,33 +296,11 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
links.add(link);
|
links.add(link);
|
||||||
link.addPacketReceiver(this);
|
link.addPacketReceiver(this);
|
||||||
|
|
||||||
this.protocolVersion = identityPacket.getInt("protocolVersion");
|
boolean hasChanges = updateDeviceInfo(link.getDeviceInfo());
|
||||||
|
|
||||||
if (identityPacket.has("deviceName")) {
|
|
||||||
this.name = identityPacket.getString("deviceName", this.name);
|
|
||||||
SharedPreferences.Editor editor = settings.edit();
|
|
||||||
editor.putString("deviceName", this.name);
|
|
||||||
editor.apply();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (identityPacket.has("deviceType")) {
|
|
||||||
this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
|
|
||||||
}
|
|
||||||
|
|
||||||
Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
|
|
||||||
|
|
||||||
Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
|
|
||||||
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
|
|
||||||
|
|
||||||
if (incomingCapabilities != null && outgoingCapabilities != null) {
|
|
||||||
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
|
|
||||||
} else {
|
|
||||||
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
|
|
||||||
}
|
|
||||||
this.incomingCapabilities = incomingCapabilities;
|
|
||||||
|
|
||||||
|
if (hasChanges || links.size() == 1) {
|
||||||
reloadPluginsFromSettings();
|
reloadPluginsFromSettings();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeLink(BaseLink link) {
|
public void removeLink(BaseLink link) {
|
||||||
@ -417,6 +318,30 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean updateDeviceInfo(@NonNull DeviceInfo newDeviceInfo) {
|
||||||
|
|
||||||
|
boolean hasChanges = false;
|
||||||
|
if (!deviceInfo.name.equals(newDeviceInfo.name) || deviceInfo.type != newDeviceInfo.type) {
|
||||||
|
hasChanges = true;
|
||||||
|
deviceInfo.name = newDeviceInfo.name;
|
||||||
|
deviceInfo.type = newDeviceInfo.type;
|
||||||
|
if (isPaired()) {
|
||||||
|
deviceInfo.saveInSettings(settings);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (deviceInfo.outgoingCapabilities != newDeviceInfo.outgoingCapabilities ||
|
||||||
|
deviceInfo.incomingCapabilities != newDeviceInfo.incomingCapabilities) {
|
||||||
|
if (newDeviceInfo.outgoingCapabilities != null && newDeviceInfo.incomingCapabilities != null) {
|
||||||
|
hasChanges = true;
|
||||||
|
Log.i("updateDeviceInfo", "Updating supported plugins according to new capabilities");
|
||||||
|
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(newDeviceInfo.incomingCapabilities, newDeviceInfo.outgoingCapabilities));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return hasChanges;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPacketReceived(@NonNull NetworkPacket np) {
|
public void onPacketReceived(@NonNull NetworkPacket np) {
|
||||||
|
|
||||||
@ -580,7 +505,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!success) {
|
if (!success) {
|
||||||
Log.e("KDE/sendPacket", "No device link (of " + links.size() + " available) could send the packet. Packet " + np.getType() + " to " + name + " lost!");
|
Log.e("KDE/sendPacket", "No device link (of " + links.size() + " available) could send the packet. Packet " + np.getType() + " to " + deviceInfo.name + " lost!");
|
||||||
}
|
}
|
||||||
|
|
||||||
return success;
|
return success;
|
||||||
@ -697,6 +622,7 @@ public class Device implements BaseLink.PacketReceiver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void reloadPluginsFromSettings() {
|
public void reloadPluginsFromSettings() {
|
||||||
|
Log.i("Device", deviceInfo.name +": reloading plugins");
|
||||||
MultiValuedMap<String, String> newPluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
MultiValuedMap<String, String> newPluginsByIncomingInterface = new ArrayListValuedHashMap<>();
|
||||||
|
|
||||||
for (String pluginKey : supportedPlugins) {
|
for (String pluginKey : supportedPlugins) {
|
||||||
|
133
src/org/kde/kdeconnect/DeviceInfo.kt
Normal file
133
src/org/kde/kdeconnect/DeviceInfo.kt
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.SharedPreferences
|
||||||
|
import android.graphics.drawable.Drawable
|
||||||
|
import android.util.Base64
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper
|
||||||
|
import org.kde.kdeconnect_tp.R
|
||||||
|
import java.security.cert.Certificate
|
||||||
|
import java.security.cert.CertificateEncodingException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeviceInfo contains all the properties needed to instantiate a Device.
|
||||||
|
*/
|
||||||
|
class DeviceInfo(
|
||||||
|
@JvmField val id : String,
|
||||||
|
@JvmField val certificate : Certificate,
|
||||||
|
@JvmField var name : String,
|
||||||
|
@JvmField var type : DeviceType,
|
||||||
|
@JvmField var protocolVersion : Int = 0,
|
||||||
|
@JvmField var incomingCapabilities : Set<String>? = null,
|
||||||
|
@JvmField var outgoingCapabilities : Set<String>? = null,
|
||||||
|
) {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the info in settings so it can be restored later using loadFromSettings().
|
||||||
|
* This is used to keep info from paired devices, even when they are not reachable.
|
||||||
|
* The capabilities and protocol version are not persisted.
|
||||||
|
*/
|
||||||
|
fun saveInSettings(settings: SharedPreferences) {
|
||||||
|
val editor = settings.edit()
|
||||||
|
try {
|
||||||
|
val encodedCertificate = Base64.encodeToString(certificate.encoded, 0)
|
||||||
|
editor.putString("certificate", encodedCertificate)
|
||||||
|
} catch (e: CertificateEncodingException) {
|
||||||
|
throw RuntimeException(e)
|
||||||
|
}
|
||||||
|
editor.putString("deviceName", name)
|
||||||
|
editor.putString("deviceType", type.toString())
|
||||||
|
editor.apply()
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serializes to a NetworkPacket, which LanLinkProvider uses to send this data over the network.
|
||||||
|
* The serialization doesn't include the certificate, since LanLink can query that from the socket.
|
||||||
|
* Can be deserialized using fromIdentityPacketAndCert(), given a certificate.
|
||||||
|
*/
|
||||||
|
fun toIdentityPacket(): NetworkPacket {
|
||||||
|
val np = NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY)
|
||||||
|
np.set("deviceId", id)
|
||||||
|
np.set("deviceName", name)
|
||||||
|
np.set("protocolVersion", protocolVersion)
|
||||||
|
np.set("deviceType", type.toString())
|
||||||
|
np.set("incomingCapabilities", incomingCapabilities)
|
||||||
|
np.set("outgoingCapabilities", outgoingCapabilities)
|
||||||
|
return np
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates a DeviceInfo object that was persisted using saveInSettings()
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun loadFromSettings(context : Context, deviceId: String, settings: SharedPreferences): DeviceInfo {
|
||||||
|
val deviceName = settings.getString("deviceName", "unknown")!!
|
||||||
|
val deviceType = DeviceType.fromString(settings.getString("deviceType", "desktop")!!)
|
||||||
|
val certificate = SslHelper.getDeviceCertificate(context, deviceId)
|
||||||
|
return DeviceInfo(id = deviceId, name = deviceName, type = deviceType, certificate = certificate)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Recreates a DeviceInfo object that was serialized using toIdentityPacket().
|
||||||
|
* Since toIdentityPacket() doesn't serialize the certificate, this needs to be passed separately.
|
||||||
|
*/
|
||||||
|
@JvmStatic
|
||||||
|
fun fromIdentityPacketAndCert(identityPacket : NetworkPacket, certificate : Certificate): DeviceInfo {
|
||||||
|
val deviceId = identityPacket.getString("deviceId")
|
||||||
|
val deviceName = identityPacket.getString("deviceName", "unknown")
|
||||||
|
val protocolVersion = identityPacket.getInt("protocolVersion")
|
||||||
|
val deviceType = DeviceType.fromString(identityPacket.getString("deviceType", "desktop"))
|
||||||
|
val incomingCapabilities = identityPacket.getStringSet("incomingCapabilities")
|
||||||
|
val outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities")
|
||||||
|
return DeviceInfo(id = deviceId, name = deviceName, type = deviceType, certificate = certificate,
|
||||||
|
protocolVersion = protocolVersion, incomingCapabilities = incomingCapabilities, outgoingCapabilities = outgoingCapabilities)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
enum class DeviceType {
|
||||||
|
Phone, Tablet, Computer, Tv;
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (this) {
|
||||||
|
Tablet -> "tablet"
|
||||||
|
Phone -> "phone"
|
||||||
|
Tv -> "tv"
|
||||||
|
else -> "desktop"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun getIcon(context: Context): Drawable? {
|
||||||
|
val drawableId: Int = when (this) {
|
||||||
|
Phone -> R.drawable.ic_device_phone_32dp
|
||||||
|
Tablet -> R.drawable.ic_device_tablet_32dp
|
||||||
|
Tv -> R.drawable.ic_device_tv_32dp
|
||||||
|
else -> R.drawable.ic_device_laptop_32dp
|
||||||
|
}
|
||||||
|
return ContextCompat.getDrawable(context, drawableId)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
@JvmStatic
|
||||||
|
fun fromString(s: String): DeviceType {
|
||||||
|
return when (s) {
|
||||||
|
"phone" -> Phone
|
||||||
|
"tablet" -> Tablet
|
||||||
|
"tv" -> Tv
|
||||||
|
else -> Computer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -19,7 +19,10 @@ import android.util.Log;
|
|||||||
import com.univocity.parsers.csv.CsvParser;
|
import com.univocity.parsers.csv.CsvParser;
|
||||||
import com.univocity.parsers.csv.CsvParserSettings;
|
import com.univocity.parsers.csv.CsvParserSettings;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.DeviceInfo;
|
||||||
|
import org.kde.kdeconnect.DeviceType;
|
||||||
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
|
||||||
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -53,13 +56,13 @@ public class DeviceHelper {
|
|||||||
return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
|
return (uiMode & Configuration.UI_MODE_TYPE_MASK) == Configuration.UI_MODE_TYPE_TELEVISION;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Device.DeviceType getDeviceType(Context context) {
|
public static DeviceType getDeviceType(Context context) {
|
||||||
if (isTv(context)) {
|
if (isTv(context)) {
|
||||||
return Device.DeviceType.Tv;
|
return DeviceType.Tv;
|
||||||
} else if (isTablet()) {
|
} else if (isTablet()) {
|
||||||
return Device.DeviceType.Tablet;
|
return DeviceType.Tablet;
|
||||||
} else {
|
} else {
|
||||||
return Device.DeviceType.Phone;
|
return DeviceType.Phone;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -146,4 +149,14 @@ public class DeviceHelper {
|
|||||||
return preferences.getString(KEY_DEVICE_ID_PREFERENCE, null);
|
return preferences.getString(KEY_DEVICE_ID_PREFERENCE, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static DeviceInfo getDeviceInfo(Context context) {
|
||||||
|
return new DeviceInfo(getDeviceId(context),
|
||||||
|
SslHelper.certificate,
|
||||||
|
getDeviceName(context),
|
||||||
|
DeviceHelper.getDeviceType(context),
|
||||||
|
ProtocolVersion,
|
||||||
|
PluginFactory.getIncomingCapabilities(),
|
||||||
|
PluginFactory.getOutgoingCapabilities());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -24,8 +24,6 @@ import org.kde.kdeconnect.Plugins.Plugin;
|
|||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
import org.kde.kdeconnect.UserInterface.ThemeUtil;
|
||||||
|
|
||||||
import java.security.cert.Certificate;
|
|
||||||
import java.security.cert.CertificateException;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
|
||||||
@ -115,14 +113,9 @@ public class KdeConnect extends Application {
|
|||||||
for (String deviceId : trustedDevices) {
|
for (String deviceId : trustedDevices) {
|
||||||
//Log.e("BackgroundService", "Loading device "+deviceId);
|
//Log.e("BackgroundService", "Loading device "+deviceId);
|
||||||
if (preferences.getBoolean(deviceId, false)) {
|
if (preferences.getBoolean(deviceId, false)) {
|
||||||
try {
|
|
||||||
Device device = new Device(this, deviceId);
|
Device device = new Device(this, deviceId);
|
||||||
devices.put(deviceId, device);
|
devices.put(deviceId, device);
|
||||||
device.addPairingCallback(devicePairingCallback);
|
device.addPairingCallback(devicePairingCallback);
|
||||||
} catch (CertificateException e) {
|
|
||||||
Log.e("KdeConnect", "Could not load trusted device, certificate not valid: " + deviceId);
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -151,18 +144,13 @@ public class KdeConnect extends Application {
|
|||||||
|
|
||||||
private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() {
|
private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() {
|
||||||
@Override
|
@Override
|
||||||
public void onConnectionReceived(@NonNull final String deviceId,
|
public void onConnectionReceived(@NonNull final BaseLink link) {
|
||||||
@NonNull final Certificate certificate,
|
Device device = devices.get(link.getDeviceId());
|
||||||
@NonNull final NetworkPacket identityPacket,
|
|
||||||
@NonNull final BaseLink link) {
|
|
||||||
Device device = devices.get(deviceId);
|
|
||||||
if (device != null) {
|
if (device != null) {
|
||||||
Log.i("KDE/Application", "addLink, known device: " + deviceId);
|
device.addLink(link);
|
||||||
device.addLink(identityPacket, link);
|
|
||||||
} else {
|
} else {
|
||||||
Log.i("KDE/Application", "addLink,unknown device: " + deviceId);
|
device = new Device(KdeConnect.this, link);
|
||||||
device = new Device(KdeConnect.this, deviceId, certificate, identityPacket, link);
|
devices.put(link.getDeviceId(), device);
|
||||||
devices.put(deviceId, device);
|
|
||||||
device.addPairingCallback(devicePairingCallback);
|
device.addPairingCallback(devicePairingCallback);
|
||||||
}
|
}
|
||||||
onDeviceListChanged();
|
onDeviceListChanged();
|
||||||
|
@ -6,15 +6,10 @@
|
|||||||
|
|
||||||
package org.kde.kdeconnect;
|
package org.kde.kdeconnect;
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.json.JSONArray;
|
import org.json.JSONArray;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
|
||||||
|
|
||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -263,26 +258,6 @@ public class NetworkPacket {
|
|||||||
return np;
|
return np;
|
||||||
}
|
}
|
||||||
|
|
||||||
static public NetworkPacket createIdentityPacket(Context context) {
|
|
||||||
|
|
||||||
NetworkPacket np = new NetworkPacket(NetworkPacket.PACKET_TYPE_IDENTITY);
|
|
||||||
|
|
||||||
String deviceId = DeviceHelper.getDeviceId(context);
|
|
||||||
try {
|
|
||||||
np.mBody.put("deviceId", deviceId);
|
|
||||||
np.mBody.put("deviceName", DeviceHelper.getDeviceName(context));
|
|
||||||
np.mBody.put("protocolVersion", DeviceHelper.ProtocolVersion);
|
|
||||||
np.mBody.put("deviceType", DeviceHelper.getDeviceType(context).toString());
|
|
||||||
np.mBody.put("incomingCapabilities", new JSONArray(PluginFactory.getIncomingCapabilities()));
|
|
||||||
np.mBody.put("outgoingCapabilities", new JSONArray(PluginFactory.getOutgoingCapabilities()));
|
|
||||||
} catch (Exception e) {
|
|
||||||
Log.e("NetworkPacket", "Exception on createIdentityPacket", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
return np;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setPayload(Payload payload) { mPayload = payload; }
|
public void setPayload(Payload payload) { mPayload = payload; }
|
||||||
|
|
||||||
public Payload getPayload() {
|
public Payload getPayload() {
|
||||||
|
@ -19,7 +19,7 @@ import android.view.KeyEvent;
|
|||||||
import androidx.annotation.DrawableRes;
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.DeviceType;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
@ -33,7 +33,7 @@ public class BigscreenPlugin extends Plugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCompatible() {
|
public boolean isCompatible() {
|
||||||
return device.getDeviceType().equals(Device.DeviceType.Tv) && super.isCompatible();
|
return device.getDeviceType().equals(DeviceType.Tv) && super.isCompatible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,7 +18,7 @@ import androidx.annotation.DrawableRes;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ArrayUtils;
|
import org.apache.commons.lang3.ArrayUtils;
|
||||||
import org.kde.kdeconnect.Device;
|
import org.kde.kdeconnect.DeviceType;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
@ -41,7 +41,7 @@ public class PresenterPlugin extends Plugin {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isCompatible() {
|
public boolean isCompatible() {
|
||||||
return !device.getDeviceType().equals(Device.DeviceType.Phone) && super.isCompatible();
|
return !device.getDeviceType().equals(DeviceType.Phone) && super.isCompatible();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -92,7 +92,7 @@ class SimpleSftpServer {
|
|||||||
sshd.setCommandFactory(new ScpCommandFactory());
|
sshd.setCommandFactory(new ScpCommandFactory());
|
||||||
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory()));
|
sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory()));
|
||||||
|
|
||||||
keyAuth.deviceKey = device.certificate.getPublicKey();
|
keyAuth.deviceKey = device.getCertificate().getPublicKey();
|
||||||
|
|
||||||
sshd.setPublickeyAuthenticator(keyAuth);
|
sshd.setPublickeyAuthenticator(keyAuth);
|
||||||
sshd.setPasswordAuthenticator(passwordAuth);
|
sshd.setPasswordAuthenticator(passwordAuth);
|
||||||
|
@ -84,7 +84,7 @@ public class DeviceTest {
|
|||||||
MockSharedPreference deviceSettings = new MockSharedPreference();
|
MockSharedPreference deviceSettings = new MockSharedPreference();
|
||||||
SharedPreferences.Editor editor = deviceSettings.edit();
|
SharedPreferences.Editor editor = deviceSettings.edit();
|
||||||
editor.putString("deviceName", name);
|
editor.putString("deviceName", name);
|
||||||
editor.putString("deviceType", Device.DeviceType.Phone.toString());
|
editor.putString("deviceType", DeviceType.Phone.toString());
|
||||||
editor.putString("certificate", encodedCertificate);
|
editor.putString("certificate", encodedCertificate);
|
||||||
editor.apply();
|
editor.apply();
|
||||||
Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings);
|
Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings);
|
||||||
@ -110,12 +110,11 @@ public class DeviceTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testDeviceType() {
|
public void testDeviceType() {
|
||||||
assertEquals(Device.DeviceType.Phone, Device.DeviceType.FromString(Device.DeviceType.Phone.toString()));
|
assertEquals(DeviceType.Phone, DeviceType.fromString(DeviceType.Phone.toString()));
|
||||||
assertEquals(Device.DeviceType.Tablet, Device.DeviceType.FromString(Device.DeviceType.Tablet.toString()));
|
assertEquals(DeviceType.Tablet, DeviceType.fromString(DeviceType.Tablet.toString()));
|
||||||
assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(Device.DeviceType.Computer.toString()));
|
assertEquals(DeviceType.Computer, DeviceType.fromString(DeviceType.Computer.toString()));
|
||||||
assertEquals(Device.DeviceType.Tv, Device.DeviceType.FromString(Device.DeviceType.Tv.toString()));
|
assertEquals(DeviceType.Tv, DeviceType.fromString(DeviceType.Tv.toString()));
|
||||||
assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(""));
|
assertEquals(DeviceType.Computer, DeviceType.fromString("invalid"));
|
||||||
assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(null));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Basic paired device testing
|
// Basic paired device testing
|
||||||
@ -124,10 +123,10 @@ public class DeviceTest {
|
|||||||
Device device = new Device(context, "testDevice");
|
Device device = new Device(context, "testDevice");
|
||||||
|
|
||||||
assertEquals(device.getDeviceId(), "testDevice");
|
assertEquals(device.getDeviceId(), "testDevice");
|
||||||
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
assertEquals(device.getDeviceType(), DeviceType.Phone);
|
||||||
assertEquals(device.getName(), "Test Device");
|
assertEquals(device.getName(), "Test Device");
|
||||||
assertTrue(device.isPaired());
|
assertTrue(device.isPaired());
|
||||||
assertNotNull(device.certificate);
|
assertNotNull(device.deviceInfo.certificate);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testPairingDone() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, CertificateException {
|
public void testPairingDone() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, CertificateException {
|
||||||
@ -137,7 +136,7 @@ public class DeviceTest {
|
|||||||
fakeNetworkPacket.set("deviceId", deviceId);
|
fakeNetworkPacket.set("deviceId", deviceId);
|
||||||
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
fakeNetworkPacket.set("deviceName", "Unpaired Test Device");
|
||||||
fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
|
fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion);
|
||||||
fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString());
|
fakeNetworkPacket.set("deviceType", DeviceType.Phone.toString());
|
||||||
String certificateString =
|
String certificateString =
|
||||||
"MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
|
"MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" +
|
||||||
"NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
|
"NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" +
|
||||||
@ -157,18 +156,21 @@ public class DeviceTest {
|
|||||||
"7n+KOQ==";
|
"7n+KOQ==";
|
||||||
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
byte[] certificateBytes = Base64.decode(certificateString, 0);
|
||||||
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
Certificate certificate = SslHelper.parseCertificate(certificateBytes);
|
||||||
|
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(fakeNetworkPacket, certificate);
|
||||||
|
|
||||||
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class);
|
||||||
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider");
|
||||||
LanLink link = Mockito.mock(LanLink.class);
|
LanLink link = Mockito.mock(LanLink.class);
|
||||||
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
|
Mockito.when(link.getLinkProvider()).thenReturn(linkProvider);
|
||||||
Device device = new Device(context, deviceId, certificate, fakeNetworkPacket, link);
|
Mockito.when(link.getDeviceId()).thenReturn(deviceId);
|
||||||
|
Mockito.when(link.getDeviceInfo()).thenReturn(deviceInfo);
|
||||||
|
Device device = new Device(context, link);
|
||||||
|
|
||||||
assertNotNull(device);
|
assertNotNull(device);
|
||||||
assertEquals(device.getDeviceId(), deviceId);
|
assertEquals(device.getDeviceId(), deviceId);
|
||||||
assertEquals(device.getName(), "Unpaired Test Device");
|
assertEquals(device.getName(), "Unpaired Test Device");
|
||||||
assertEquals(device.getDeviceType(), Device.DeviceType.Phone);
|
assertEquals(device.getDeviceType(), DeviceType.Phone);
|
||||||
assertNotNull(device.certificate);
|
assertNotNull(device.deviceInfo.certificate);
|
||||||
|
|
||||||
Method method = PairingHandler.class.getDeclaredMethod("pairingDone");
|
Method method = PairingHandler.class.getDeclaredMethod("pairingDone");
|
||||||
method.setAccessible(true);
|
method.setAccessible(true);
|
||||||
|
@ -10,10 +10,7 @@ import static org.junit.Assert.assertEquals;
|
|||||||
import static org.junit.Assert.assertFalse;
|
import static org.junit.Assert.assertFalse;
|
||||||
import static org.junit.Assert.assertTrue;
|
import static org.junit.Assert.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyInt;
|
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
@ -22,10 +19,13 @@ import org.junit.Test;
|
|||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
import org.mockito.internal.util.collections.Sets;
|
||||||
import org.powermock.api.mockito.PowerMockito;
|
import org.powermock.api.mockito.PowerMockito;
|
||||||
import org.powermock.core.classloader.annotations.PrepareForTest;
|
import org.powermock.core.classloader.annotations.PrepareForTest;
|
||||||
import org.powermock.modules.junit4.PowerMockRunner;
|
import org.powermock.modules.junit4.PowerMockRunner;
|
||||||
|
|
||||||
|
import java.security.cert.Certificate;
|
||||||
|
|
||||||
@RunWith(PowerMockRunner.class)
|
@RunWith(PowerMockRunner.class)
|
||||||
@PrepareForTest({DeviceHelper.class, Log.class})
|
@PrepareForTest({DeviceHelper.class, Log.class})
|
||||||
public class NetworkPacketTest {
|
public class NetworkPacketTest {
|
||||||
@ -34,7 +34,7 @@ public class NetworkPacketTest {
|
|||||||
public void setUp() {
|
public void setUp() {
|
||||||
PowerMockito.mockStatic(DeviceHelper.class);
|
PowerMockito.mockStatic(DeviceHelper.class);
|
||||||
PowerMockito.when(DeviceHelper.getDeviceId(any())).thenReturn("123");
|
PowerMockito.when(DeviceHelper.getDeviceId(any())).thenReturn("123");
|
||||||
PowerMockito.when(DeviceHelper.getDeviceType(any())).thenReturn(Device.DeviceType.Phone);
|
PowerMockito.when(DeviceHelper.getDeviceType(any())).thenReturn(DeviceType.Phone);
|
||||||
|
|
||||||
PowerMockito.mockStatic(Log.class);
|
PowerMockito.mockStatic(Log.class);
|
||||||
}
|
}
|
||||||
@ -70,14 +70,23 @@ public class NetworkPacketTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testIdentity() {
|
public void testIdentity() {
|
||||||
|
Certificate cert = Mockito.mock(Certificate.class);
|
||||||
|
|
||||||
Context context = Mockito.mock(Context.class);
|
DeviceInfo deviceInfo = new DeviceInfo("myid", cert, "myname", DeviceType.Tv, 12, Sets.newSet("ASDFG"), Sets.newSet("QWERTY"));
|
||||||
MockSharedPreference settings = new MockSharedPreference();
|
|
||||||
Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(settings);
|
|
||||||
|
|
||||||
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
|
NetworkPacket np = deviceInfo.toIdentityPacket();
|
||||||
|
|
||||||
|
assertEquals(np.getInt("protocolVersion"), 12);
|
||||||
|
|
||||||
|
DeviceInfo parsed = DeviceInfo.fromIdentityPacketAndCert(np, cert);
|
||||||
|
|
||||||
|
assertEquals(parsed.name, deviceInfo.name);
|
||||||
|
assertEquals(parsed.id, deviceInfo.id);
|
||||||
|
assertEquals(parsed.type, deviceInfo.type);
|
||||||
|
assertEquals(parsed.protocolVersion, deviceInfo.protocolVersion);
|
||||||
|
assertEquals(parsed.incomingCapabilities, deviceInfo.incomingCapabilities);
|
||||||
|
assertEquals(parsed.outgoingCapabilities, deviceInfo.outgoingCapabilities);
|
||||||
|
|
||||||
assertEquals(np.getInt("protocolVersion"), DeviceHelper.ProtocolVersion);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user