diff --git a/src/org/kde/kdeconnect/Backends/BaseLink.java b/src/org/kde/kdeconnect/Backends/BaseLink.java index 0cc82ddb..cc4dacb1 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLink.java +++ b/src/org/kde/kdeconnect/Backends/BaseLink.java @@ -12,6 +12,7 @@ import androidx.annotation.NonNull; import androidx.annotation.WorkerThread; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.DeviceInfo; import org.kde.kdeconnect.NetworkPacket; import java.io.IOException; @@ -26,20 +27,20 @@ public abstract class BaseLink { protected final Context context; private final BaseLinkProvider linkProvider; - private final String deviceId; private final ArrayList 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.linkProvider = linkProvider; - this.deviceId = deviceId; } /* To be implemented by each link for pairing handlers */ public abstract String getName(); + public abstract DeviceInfo getDeviceInfo(); + public String getDeviceId() { - return deviceId; + return getDeviceInfo().id; } public BaseLinkProvider getLinkProvider() { @@ -54,7 +55,7 @@ public abstract class BaseLink { } //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) { pr.onPacketReceived(np); } diff --git a/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java b/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java index aee9c1f0..7291d8be 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java @@ -8,23 +8,17 @@ package org.kde.kdeconnect.Backends; import androidx.annotation.NonNull; -import org.kde.kdeconnect.NetworkPacket; - -import java.security.cert.Certificate; import java.util.concurrent.CopyOnWriteArrayList; public abstract class BaseLinkProvider { - private final CopyOnWriteArrayList connectionReceivers = new CopyOnWriteArrayList<>(); - public interface ConnectionReceiver { - void onConnectionReceived(@NonNull final String deviceId, - @NonNull final Certificate certificate, - @NonNull final NetworkPacket identityPacket, - @NonNull final BaseLink link); + void onConnectionReceived(@NonNull final BaseLink link); void onConnectionLost(BaseLink link); } + private final CopyOnWriteArrayList connectionReceivers = new CopyOnWriteArrayList<>(); + public void addConnectionReceiver(ConnectionReceiver 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 */ - protected void onConnectionReceived(@NonNull final String deviceId, - @NonNull final Certificate certificate, - @NonNull final NetworkPacket identityPacket, - @NonNull final BaseLink link) { + protected void onConnectionReceived(@NonNull final BaseLink link) { //Log.i("KDE/LinkProvider", "onConnectionReceived"); for(ConnectionReceiver cr : connectionReceivers) { - cr.onConnectionReceived(deviceId, certificate, identityPacket, link); + cr.onConnectionReceived(link); } } diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java index ce6af007..673f2709 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java @@ -17,6 +17,7 @@ import org.json.JSONException; import org.json.JSONObject; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.DeviceInfo; import org.kde.kdeconnect.NetworkPacket; import java.io.IOException; @@ -34,6 +35,7 @@ public class BluetoothLink extends BaseLink { private final OutputStream output; private final BluetoothDevice remoteAddress; private final BluetoothLinkProvider linkProvider; + private final DeviceInfo deviceInfo; 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) { - super(context, deviceId, linkProvider); + public BluetoothLink(Context context, ConnectionMultiplexer connection, InputStream input, OutputStream output, BluetoothDevice remoteAddress, DeviceInfo deviceInfo, BluetoothLinkProvider linkProvider) { + super(context, linkProvider); this.connection = connection; this.input = input; this.output = output; + this.deviceInfo = deviceInfo; this.remoteAddress = remoteAddress; this.linkProvider = linkProvider; } @@ -111,6 +114,11 @@ public class BluetoothLink extends BaseLink { return "BluetoothLink"; } + @Override + public DeviceInfo getDeviceInfo() { + return deviceInfo; + } + public void disconnect() { if (connection == null) { return; @@ -120,7 +128,7 @@ public class BluetoothLink extends BaseLink { connection.close(); } catch (IOException ignored) { } - linkProvider.disconnectedLink(this, getDeviceId(), remoteAddress); + linkProvider.disconnectedLink(this, remoteAddress); } private void sendMessage(NetworkPacket np) throws JSONException, IOException { diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java index eaa4aadc..9c696ef7 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java @@ -20,6 +20,8 @@ import android.util.Log; import org.kde.kdeconnect.Backends.BaseLinkProvider; 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.ThreadHelper; import org.kde.kdeconnect.NetworkPacket; @@ -54,10 +56,6 @@ public class BluetoothLinkProvider extends BaseLinkProvider { private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException { 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); BluetoothLink oldLink = visibleDevices.get(deviceId); if (oldLink == link) { @@ -65,8 +63,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider { return; } visibleDevices.put(deviceId, link); - onConnectionReceived(deviceId, certificate, identityPacket, link); + onConnectionReceived(link); link.startListening(); + link.packetReceived(identityPacket); if (oldLink != null) { Log.i("BluetoothLinkProvider", "Removing old connection to same device"); oldLink.disconnect(); @@ -127,9 +126,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider { return "BluetoothLinkProvider"; } - public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothDevice remoteAddress) { + public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) { sockets.remove(remoteAddress); - visibleDevices.remove(deviceId); + visibleDevices.remove(link.getDeviceId()); onConnectionLost(link); } @@ -196,8 +195,10 @@ public class BluetoothLinkProvider extends BaseLinkProvider { OutputStream outputStream = connection.getDefaultOutputStream(); 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)); + byte[] message = np.serialize().getBytes(Charsets.UTF_8); outputStream.write(message); outputStream.flush(); @@ -223,9 +224,15 @@ public class BluetoothLinkProvider extends BaseLinkProvider { 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, inputStream, outputStream, socket.getRemoteDevice(), - identityPacket.getString("deviceId"), BluetoothLinkProvider.this); + deviceInfo, BluetoothLinkProvider.this); addLink(identityPacket, link); } catch (Exception e) { synchronized (sockets) { @@ -360,7 +367,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider { 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)) { // Probably won't happen, but just to be safe connection.close(); @@ -373,10 +380,18 @@ public class BluetoothLinkProvider extends BaseLinkProvider { Log.i("BTLinkProvider/Client", "identity packet received, creating link"); - final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream, - socket.getRemoteDevice(), identityPacket.getString("deviceId"), BluetoothLinkProvider.this); + String certificateString = identityPacket.getString("certificate"); + 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() { @Override public void onSuccess() { diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java index d7590edd..c6ed26d9 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -16,6 +16,7 @@ import org.json.JSONObject; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.DeviceInfo; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.ThreadHelper; import org.kde.kdeconnect.NetworkPacket; @@ -42,6 +43,8 @@ public class LanLink extends BaseLink { Locally, Remotely } + private final DeviceInfo deviceInfo; + private volatile SSLSocket socket = null; @Override @@ -99,8 +102,9 @@ public class LanLink extends BaseLink { return oldSocket; } - public LanLink(Context context, String deviceId, BaseLinkProvider linkProvider, SSLSocket socket) throws IOException { - super(context, deviceId, linkProvider); + public LanLink(@NonNull Context context, @NonNull DeviceInfo deviceInfo, @NonNull BaseLinkProvider linkProvider, @NonNull SSLSocket socket) throws IOException { + super(context, linkProvider); + this.deviceInfo = deviceInfo; reset(socket); } @@ -109,6 +113,11 @@ public class LanLink extends BaseLink { return "LanLink"; } + @Override + public DeviceInfo getDeviceInfo() { + return deviceInfo; + } + @WorkerThread @Override public boolean sendPacket(@NonNull NetworkPacket np, @NonNull final Device.SendPacketStatusCallback callback, boolean sendPayloadFromSameThread) { diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index 9db86346..a928ee26 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -17,6 +17,7 @@ import org.json.JSONException; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; 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.ThreadHelper; @@ -51,7 +52,7 @@ import kotlin.text.Charsets; /** * This LanLinkProvider creates {@link LanLink}s to other devices on the same * 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) */ @@ -136,8 +137,10 @@ public class LanLinkProvider extends BaseLinkProvider { Socket socket = socketFactory.createSocket(address, tcpPort); configureSocket(socket); + DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context); + NetworkPacket myIdentity = myDeviceInfo.toIdentityPacket(); + OutputStream out = socket.getOutputStream(); - NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context); out.write(myIdentity.serialize().getBytes()); out.flush(); @@ -193,17 +196,19 @@ public class LanLinkProvider extends BaseLinkProvider { 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); sslSocket.addHandshakeCompletedListener(event -> { String mode = clientMode ? "client" : "server"; try { Certificate certificate = event.getPeerCertificates()[0]; - Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite()); - addLink(deviceId, certificate, identityPacket, sslSocket); - } catch (Exception e) { - Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e); + DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate); + Log.i("KDE/LanLinkProvider", "Handshake as " + mode + " successful with " + deviceName + " secured with " + event.getCipherSuite()); + addLink(sslSocket, deviceInfo); + } catch (IOException e) { + Log.e("KDE/LanLinkProvider", "Handshake as " + mode + " failed with " + deviceName, e); Device device = KdeConnect.getInstance().getDevice(deviceId); if (device == null) { return; @@ -221,25 +226,24 @@ public class LanLinkProvider extends BaseLinkProvider { /** * 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 deviceInfo remote device info * @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 { - LanLink currentLink = visibleDevices.get(deviceId); - if (currentLink != null) { - //Update old link - Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId); - final Socket oldSocket = currentLink.reset(socket); + private LanLink addLink(SSLSocket socket, DeviceInfo deviceInfo) throws IOException { + LanLink link = visibleDevices.get(deviceInfo.id); + if (link != null) { + // Update existing link + Log.d("KDE/LanLinkProvider", "Reusing same link for device " + deviceInfo.id); + final Socket oldSocket = link.reset(socket); } else { - Log.i("KDE/LanLinkProvider", "Creating a new link for device " + deviceId); - //Let's create the link - LanLink link = new LanLink(context, deviceId, this, socket); - visibleDevices.put(deviceId, link); - onConnectionReceived(deviceId, certificate, identityPacket, link); + // Create a new link + Log.d("KDE/LanLinkProvider", "Creating a new link for device " + deviceInfo.id); + link = new LanLink(context, deviceInfo, this, socket); + visibleDevices.put(deviceInfo.id, link); + onConnectionReceived(link); } + return link; } public LanLinkProvider(Context context) { @@ -378,7 +382,8 @@ public class LanLinkProvider extends BaseLinkProvider { return; } - NetworkPacket identity = NetworkPacket.createIdentityPacket(context); + DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context); + NetworkPacket identity = myDeviceInfo.toIdentityPacket(); identity.set("tcpPort", tcpServer.getLocalPort()); byte[] bytes; diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java index 118148a4..08f38546 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -14,12 +14,14 @@ import androidx.annotation.WorkerThread; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.DeviceInfo; +import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.NetworkPacket; public class LoopbackLink extends BaseLink { public LoopbackLink(Context context, BaseLinkProvider linkProvider) { - super(context, "loopback", linkProvider); + super(context, linkProvider); } @Override @@ -40,4 +42,9 @@ public class LoopbackLink extends BaseLink { return true; } + @Override + public DeviceInfo getDeviceInfo() { + return DeviceHelper.getDeviceInfo(context); + } + } diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java index 5c71d9ba..63e19605 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java @@ -9,9 +9,6 @@ package org.kde.kdeconnect.Backends.LoopbackBackend; import android.content.Context; 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 { @@ -32,9 +29,8 @@ public class LoopbackLinkProvider extends BaseLinkProvider { @Override public void onNetworkChange() { - NetworkPacket np = NetworkPacket.createIdentityPacket(context); - String deviceId = DeviceHelper.getDeviceId(context); - onConnectionReceived(deviceId, SslHelper.certificate, np, new LoopbackLink(context, this)); + LoopbackLink link = new LoopbackLink(context, this); + onConnectionReceived(link); } @Override diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java index a0110ec2..5d633aa3 100644 --- a/src/org/kde/kdeconnect/Device.java +++ b/src/org/kde/kdeconnect/Device.java @@ -14,7 +14,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.content.res.Resources; import android.graphics.drawable.Drawable; -import android.util.Base64; import android.util.Log; import androidx.annotation.AnyThread; @@ -26,7 +25,6 @@ import androidx.core.content.ContextCompat; import org.apache.commons.collections4.MultiValuedMap; import org.apache.commons.collections4.multimap.ArrayListValuedHashMap; -import org.apache.commons.lang3.StringUtils; import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.kde.kdeconnect.Helpers.NotificationHelper; @@ -38,13 +36,8 @@ import org.kde.kdeconnect_tp.R; import java.io.IOException; 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.HashSet; import java.util.List; -import java.util.Set; import java.util.Vector; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; @@ -53,30 +46,26 @@ public class Device implements BaseLink.PacketReceiver { private final Context context; - private final String deviceId; - private String name; - public Certificate certificate; + final DeviceInfo deviceInfo; + private int notificationId; - private int protocolVersion; - private DeviceType deviceType; PairingHandler pairingHandler; private final CopyOnWriteArrayList pairingCallbacks = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList links = new CopyOnWriteArrayList<>(); private DevicePacketQueue packetQueue; - private List supportedPlugins = new ArrayList<>(); + private List supportedPlugins; private final ConcurrentHashMap plugins = new ConcurrentHashMap<>(); private final ConcurrentHashMap pluginsWithoutPermissions = new ConcurrentHashMap<>(); private final ConcurrentHashMap pluginsWithoutOptionalPermissions = new ConcurrentHashMap<>(); private MultiValuedMap pluginsByIncomingInterface = new ArrayListValuedHashMap<>(); private final SharedPreferences settings; private final CopyOnWriteArrayList pluginsChangedListeners = new CopyOnWriteArrayList<>(); - private Set incomingCapabilities = new HashSet<>(); public boolean supportsPacketType(String type) { - if (incomingCapabilities == null) { + if (deviceInfo.incomingCapabilities == null) { return true; } 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); } - public enum DeviceType { - Phone, - Tablet, - Computer, - Tv; - - 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 { - + /** + * Constructor for remembered, already-trusted devices. + * Given the deviceId, it will load the other properties from SharedPreferences. + */ + Device(@NonNull Context context, @NonNull String deviceId) { this.context = context; - 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.deviceId = deviceId; - 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(); + this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities + Log.i("Device","Loading trusted device: " + deviceInfo.name); } - // 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) { - Log.i("Device","Creating untrusted device"); - + /** + * Constructor for devices discovered but not trusted yet. + * 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.settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE); + this.deviceInfo = link.getDeviceInfo(); + this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE); this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired); - - this.deviceId = deviceId; - this.certificate = certificate; - - // 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); + this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities + Log.i("Device","Creating untrusted device: "+ deviceInfo.name); + addLink(link); } public String getName() { - return StringUtils.defaultString(name, context.getString(R.string.unknown_device)); + return deviceInfo.name; } public Drawable getIcon() { - return deviceType.getIcon(context); + return deviceInfo.type.getIcon(context); } public DeviceType getDeviceType() { - return deviceType; + return deviceInfo.type; } public String getDeviceId() { - return deviceId; + return deviceInfo.id; + } + + public Certificate getCertificate() { + return deviceInfo.certificate; } 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 public int compareProtocolVersion() { - return protocolVersion - DeviceHelper.ProtocolVersion; + return deviceInfo.protocolVersion - DeviceHelper.ProtocolVersion; } @@ -257,20 +189,11 @@ public class Device implements BaseLink.PacketReceiver { hidePairingNotification(); // Store current device certificate so we can check it in the future (TOFU) - SharedPreferences.Editor editor = context.getSharedPreferences(getDeviceId(), Context.MODE_PRIVATE).edit(); - 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(); + deviceInfo.saveInSettings(Device.this.settings); // Store as trusted device SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE); - preferences.edit().putBoolean(deviceId, true).apply(); + preferences.edit().putBoolean(deviceInfo.id, true).apply(); reloadPluginsFromSettings(); @@ -290,9 +213,9 @@ public class Device implements BaseLink.PacketReceiver { @Override public void unpaired() { 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(); for (PairingHandler.PairingCallback cb : pairingCallbacks) { @@ -334,7 +257,7 @@ public class Device implements BaseLink.PacketReceiver { 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) .setContentTitle(res.getString(R.string.pairing_request_from, getName())) @@ -365,7 +288,7 @@ public class Device implements BaseLink.PacketReceiver { return !links.isEmpty(); } - public void addLink(NetworkPacket identityPacket, BaseLink link) { + public void addLink(BaseLink link) { if (links.isEmpty()) { packetQueue = new DevicePacketQueue(this); } @@ -373,33 +296,11 @@ public class Device implements BaseLink.PacketReceiver { links.add(link); 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 (hasChanges || links.size() == 1) { + reloadPluginsFromSettings(); } - - 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 outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null); - Set 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; - - reloadPluginsFromSettings(); - } 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 public void onPacketReceived(@NonNull NetworkPacket np) { @@ -580,7 +505,7 @@ public class Device implements BaseLink.PacketReceiver { } 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; @@ -697,6 +622,7 @@ public class Device implements BaseLink.PacketReceiver { } public void reloadPluginsFromSettings() { + Log.i("Device", deviceInfo.name +": reloading plugins"); MultiValuedMap newPluginsByIncomingInterface = new ArrayListValuedHashMap<>(); for (String pluginKey : supportedPlugins) { diff --git a/src/org/kde/kdeconnect/DeviceInfo.kt b/src/org/kde/kdeconnect/DeviceInfo.kt new file mode 100644 index 00000000..b03b1f4c --- /dev/null +++ b/src/org/kde/kdeconnect/DeviceInfo.kt @@ -0,0 +1,133 @@ +/* + * SPDX-FileCopyrightText: 2023 Albert Vaca Cintora + * + * 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? = null, + @JvmField var outgoingCapabilities : Set? = 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 + } + } + } +} diff --git a/src/org/kde/kdeconnect/Helpers/DeviceHelper.java b/src/org/kde/kdeconnect/Helpers/DeviceHelper.java index df89ec69..7af5a80f 100644 --- a/src/org/kde/kdeconnect/Helpers/DeviceHelper.java +++ b/src/org/kde/kdeconnect/Helpers/DeviceHelper.java @@ -19,7 +19,10 @@ import android.util.Log; import com.univocity.parsers.csv.CsvParser; 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.IOException; @@ -53,13 +56,13 @@ public class DeviceHelper { 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)) { - return Device.DeviceType.Tv; + return DeviceType.Tv; } else if (isTablet()) { - return Device.DeviceType.Tablet; + return DeviceType.Tablet; } else { - return Device.DeviceType.Phone; + return DeviceType.Phone; } } @@ -146,4 +149,14 @@ public class DeviceHelper { 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()); + } + } diff --git a/src/org/kde/kdeconnect/KdeConnect.java b/src/org/kde/kdeconnect/KdeConnect.java index 0d106c2c..3b13b5b7 100644 --- a/src/org/kde/kdeconnect/KdeConnect.java +++ b/src/org/kde/kdeconnect/KdeConnect.java @@ -2,7 +2,7 @@ * SPDX-FileCopyrightText: 2023 Albert Vaca Cintora * * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL -*/ + */ package org.kde.kdeconnect; @@ -24,8 +24,6 @@ import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.UserInterface.ThemeUtil; -import java.security.cert.Certificate; -import java.security.cert.CertificateException; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -115,14 +113,9 @@ public class KdeConnect extends Application { for (String deviceId : trustedDevices) { //Log.e("BackgroundService", "Loading device "+deviceId); if (preferences.getBoolean(deviceId, false)) { - try { - Device device = new Device(this, deviceId); - devices.put(deviceId, device); - device.addPairingCallback(devicePairingCallback); - } catch (CertificateException e) { - Log.e("KdeConnect", "Could not load trusted device, certificate not valid: " + deviceId); - e.printStackTrace(); - } + Device device = new Device(this, deviceId); + devices.put(deviceId, device); + device.addPairingCallback(devicePairingCallback); } } } @@ -151,18 +144,13 @@ public class KdeConnect extends Application { private final BaseLinkProvider.ConnectionReceiver connectionListener = new BaseLinkProvider.ConnectionReceiver() { @Override - public void onConnectionReceived(@NonNull final String deviceId, - @NonNull final Certificate certificate, - @NonNull final NetworkPacket identityPacket, - @NonNull final BaseLink link) { - Device device = devices.get(deviceId); + public void onConnectionReceived(@NonNull final BaseLink link) { + Device device = devices.get(link.getDeviceId()); if (device != null) { - Log.i("KDE/Application", "addLink, known device: " + deviceId); - device.addLink(identityPacket, link); + device.addLink(link); } else { - Log.i("KDE/Application", "addLink,unknown device: " + deviceId); - device = new Device(KdeConnect.this, deviceId, certificate, identityPacket, link); - devices.put(deviceId, device); + device = new Device(KdeConnect.this, link); + devices.put(link.getDeviceId(), device); device.addPairingCallback(devicePairingCallback); } onDeviceListChanged(); diff --git a/src/org/kde/kdeconnect/NetworkPacket.java b/src/org/kde/kdeconnect/NetworkPacket.java index 7b37db4f..bf87fe7f 100644 --- a/src/org/kde/kdeconnect/NetworkPacket.java +++ b/src/org/kde/kdeconnect/NetworkPacket.java @@ -6,15 +6,10 @@ package org.kde.kdeconnect; -import android.content.Context; -import android.util.Log; - import org.apache.commons.io.IOUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; -import org.kde.kdeconnect.Helpers.DeviceHelper; -import org.kde.kdeconnect.Plugins.PluginFactory; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -263,26 +258,6 @@ public class NetworkPacket { 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 Payload getPayload() { diff --git a/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenPlugin.java b/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenPlugin.java index 665f278a..ded1ccca 100644 --- a/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/BigscreenPlugin/BigscreenPlugin.java @@ -19,7 +19,7 @@ import android.view.KeyEvent; import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; -import org.kde.kdeconnect.Device; +import org.kde.kdeconnect.DeviceType; import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; @@ -33,7 +33,7 @@ public class BigscreenPlugin extends Plugin { @Override public boolean isCompatible() { - return device.getDeviceType().equals(Device.DeviceType.Tv) && super.isCompatible(); + return device.getDeviceType().equals(DeviceType.Tv) && super.isCompatible(); } @Override diff --git a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java index 2e3c0cec..9f260657 100644 --- a/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java +++ b/src/org/kde/kdeconnect/Plugins/PresenterPlugin/PresenterPlugin.java @@ -18,7 +18,7 @@ import androidx.annotation.DrawableRes; import androidx.annotation.NonNull; 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.Plugins.Plugin; import org.kde.kdeconnect.Plugins.PluginFactory; @@ -41,7 +41,7 @@ public class PresenterPlugin extends Plugin { @Override public boolean isCompatible() { - return !device.getDeviceType().equals(Device.DeviceType.Phone) && super.isCompatible(); + return !device.getDeviceType().equals(DeviceType.Phone) && super.isCompatible(); } @Override diff --git a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java index 8ce26dbd..80d023b1 100644 --- a/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java +++ b/src/org/kde/kdeconnect/Plugins/SftpPlugin/SimpleSftpServer.java @@ -92,7 +92,7 @@ class SimpleSftpServer { sshd.setCommandFactory(new ScpCommandFactory()); sshd.setSubsystemFactories(Collections.singletonList(new SftpSubsystem.Factory())); - keyAuth.deviceKey = device.certificate.getPublicKey(); + keyAuth.deviceKey = device.getCertificate().getPublicKey(); sshd.setPublickeyAuthenticator(keyAuth); sshd.setPasswordAuthenticator(passwordAuth); diff --git a/tests/org/kde/kdeconnect/DeviceTest.java b/tests/org/kde/kdeconnect/DeviceTest.java index 23c4bd13..4018f057 100644 --- a/tests/org/kde/kdeconnect/DeviceTest.java +++ b/tests/org/kde/kdeconnect/DeviceTest.java @@ -84,7 +84,7 @@ public class DeviceTest { MockSharedPreference deviceSettings = new MockSharedPreference(); SharedPreferences.Editor editor = deviceSettings.edit(); editor.putString("deviceName", name); - editor.putString("deviceType", Device.DeviceType.Phone.toString()); + editor.putString("deviceType", DeviceType.Phone.toString()); editor.putString("certificate", encodedCertificate); editor.apply(); Mockito.when(context.getSharedPreferences(eq(deviceId), eq(Context.MODE_PRIVATE))).thenReturn(deviceSettings); @@ -110,12 +110,11 @@ public class DeviceTest { @Test public void testDeviceType() { - assertEquals(Device.DeviceType.Phone, Device.DeviceType.FromString(Device.DeviceType.Phone.toString())); - assertEquals(Device.DeviceType.Tablet, Device.DeviceType.FromString(Device.DeviceType.Tablet.toString())); - assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(Device.DeviceType.Computer.toString())); - assertEquals(Device.DeviceType.Tv, Device.DeviceType.FromString(Device.DeviceType.Tv.toString())); - assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString("")); - assertEquals(Device.DeviceType.Computer, Device.DeviceType.FromString(null)); + assertEquals(DeviceType.Phone, DeviceType.fromString(DeviceType.Phone.toString())); + assertEquals(DeviceType.Tablet, DeviceType.fromString(DeviceType.Tablet.toString())); + assertEquals(DeviceType.Computer, DeviceType.fromString(DeviceType.Computer.toString())); + assertEquals(DeviceType.Tv, DeviceType.fromString(DeviceType.Tv.toString())); + assertEquals(DeviceType.Computer, DeviceType.fromString("invalid")); } // Basic paired device testing @@ -124,10 +123,10 @@ public class DeviceTest { Device device = new Device(context, "testDevice"); assertEquals(device.getDeviceId(), "testDevice"); - assertEquals(device.getDeviceType(), Device.DeviceType.Phone); + assertEquals(device.getDeviceType(), DeviceType.Phone); assertEquals(device.getName(), "Test Device"); assertTrue(device.isPaired()); - assertNotNull(device.certificate); + assertNotNull(device.deviceInfo.certificate); } public void testPairingDone() throws InvocationTargetException, IllegalAccessException, NoSuchMethodException, CertificateException { @@ -137,7 +136,7 @@ public class DeviceTest { fakeNetworkPacket.set("deviceId", deviceId); fakeNetworkPacket.set("deviceName", "Unpaired Test Device"); fakeNetworkPacket.set("protocolVersion", DeviceHelper.ProtocolVersion); - fakeNetworkPacket.set("deviceType", Device.DeviceType.Phone.toString()); + fakeNetworkPacket.set("deviceType", DeviceType.Phone.toString()); String certificateString = "MIIDVzCCAj+gAwIBAgIBCjANBgkqhkiG9w0BAQUFADBVMS8wLQYDVQQDDCZfZGExNzlhOTFfZjA2\n" + "NF80NzhlX2JlOGNfMTkzNWQ3NTQ0ZDU0XzEMMAoGA1UECgwDS0RFMRQwEgYDVQQLDAtLZGUgY29u\n" + @@ -157,18 +156,21 @@ public class DeviceTest { "7n+KOQ=="; byte[] certificateBytes = Base64.decode(certificateString, 0); Certificate certificate = SslHelper.parseCertificate(certificateBytes); + DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(fakeNetworkPacket, certificate); LanLinkProvider linkProvider = Mockito.mock(LanLinkProvider.class); Mockito.when(linkProvider.getName()).thenReturn("LanLinkProvider"); LanLink link = Mockito.mock(LanLink.class); 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); assertEquals(device.getDeviceId(), deviceId); assertEquals(device.getName(), "Unpaired Test Device"); - assertEquals(device.getDeviceType(), Device.DeviceType.Phone); - assertNotNull(device.certificate); + assertEquals(device.getDeviceType(), DeviceType.Phone); + assertNotNull(device.deviceInfo.certificate); Method method = PairingHandler.class.getDeclaredMethod("pairingDone"); method.setAccessible(true); diff --git a/tests/org/kde/kdeconnect/NetworkPacketTest.java b/tests/org/kde/kdeconnect/NetworkPacketTest.java index 4279f00e..c5c009b9 100644 --- a/tests/org/kde/kdeconnect/NetworkPacketTest.java +++ b/tests/org/kde/kdeconnect/NetworkPacketTest.java @@ -10,10 +10,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; 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 org.json.JSONException; @@ -22,10 +19,13 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.kde.kdeconnect.Helpers.DeviceHelper; import org.mockito.Mockito; +import org.mockito.internal.util.collections.Sets; import org.powermock.api.mockito.PowerMockito; import org.powermock.core.classloader.annotations.PrepareForTest; import org.powermock.modules.junit4.PowerMockRunner; +import java.security.cert.Certificate; + @RunWith(PowerMockRunner.class) @PrepareForTest({DeviceHelper.class, Log.class}) public class NetworkPacketTest { @@ -34,7 +34,7 @@ public class NetworkPacketTest { public void setUp() { PowerMockito.mockStatic(DeviceHelper.class); 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); } @@ -70,14 +70,23 @@ public class NetworkPacketTest { @Test public void testIdentity() { + Certificate cert = Mockito.mock(Certificate.class); - Context context = Mockito.mock(Context.class); - MockSharedPreference settings = new MockSharedPreference(); - Mockito.when(context.getSharedPreferences(anyString(), anyInt())).thenReturn(settings); + DeviceInfo deviceInfo = new DeviceInfo("myid", cert, "myname", DeviceType.Tv, 12, Sets.newSet("ASDFG"), Sets.newSet("QWERTY")); - 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); }