mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-09-02 15:15:09 +00:00
Compare commits
7 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
268bc833be | ||
|
d62a7fbcdc | ||
|
8c9fc6586b | ||
|
0d658e6fb6 | ||
|
020382931c | ||
|
cc0b94bd3d | ||
|
5c0c190f5a |
@@ -547,4 +547,6 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
|||||||
|
|
||||||
<string name="plugin_stats">Plugin stats</string>
|
<string name="plugin_stats">Plugin stats</string>
|
||||||
|
|
||||||
|
<string name="enable_udp_broadcast">Enable backwards-compatible device discovery</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@@ -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;
|
||||||
@@ -24,6 +25,7 @@ import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
|
|||||||
import org.kde.kdeconnect.KdeConnect;
|
import org.kde.kdeconnect.KdeConnect;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
||||||
|
import org.kde.kdeconnect.UserInterface.SettingsFragment;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -50,7 +52,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)
|
||||||
*/
|
*/
|
||||||
@@ -65,10 +67,12 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
|
final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
|
||||||
|
|
||||||
private ServerSocket tcpServer;
|
ServerSocket tcpServer;
|
||||||
private DatagramSocket udpServer;
|
DatagramSocket udpServer;
|
||||||
|
|
||||||
|
MdnsDiscovery mdnsDiscovery;
|
||||||
|
|
||||||
private long lastBroadcast = 0;
|
private long lastBroadcast = 0;
|
||||||
private final static long delayBetweenBroadcasts = 200;
|
private final static long delayBetweenBroadcasts = 200;
|
||||||
@@ -133,8 +137,10 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
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 +196,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,29 +226,29 @@ 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 LanLink 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
|
// Update existing link
|
||||||
Log.i("KDE/LanLinkProvider", "Reusing same link for device " + deviceId);
|
Log.d("KDE/LanLinkProvider", "Reusing same link for device " + deviceInfo.id);
|
||||||
final Socket oldSocket = currentLink.reset(socket);
|
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);
|
||||||
}
|
}
|
||||||
|
return link;
|
||||||
}
|
}
|
||||||
|
|
||||||
public LanLinkProvider(Context context) {
|
public LanLinkProvider(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.mdnsDiscovery = new MdnsDiscovery(context, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupUdpListener() {
|
private void setupUdpListener() {
|
||||||
@@ -328,6 +336,12 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void broadcastUdpIdentityPacket() {
|
private void broadcastUdpIdentityPacket() {
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
|
if (!preferences.getBoolean(SettingsFragment.KEY_UDP_BROADCAST_ENABLED, true)) {
|
||||||
|
Log.i("LanLinkProvider", "UDP broadcast is disabled in settings. Skipping.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (System.currentTimeMillis() < lastBroadcast + delayBetweenBroadcasts) {
|
if (System.currentTimeMillis() < lastBroadcast + delayBetweenBroadcasts) {
|
||||||
Log.i("LanLinkProvider", "broadcastUdpPacket: relax cowboy");
|
Log.i("LanLinkProvider", "broadcastUdpPacket: relax cowboy");
|
||||||
return;
|
return;
|
||||||
@@ -368,7 +382,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;
|
||||||
@@ -411,6 +426,9 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
setupUdpListener();
|
setupUdpListener();
|
||||||
setupTcpListener();
|
setupTcpListener();
|
||||||
|
|
||||||
|
mdnsDiscovery.startListening();
|
||||||
|
mdnsDiscovery.startAnnouncing();
|
||||||
|
|
||||||
broadcastUdpIdentityPacket();
|
broadcastUdpIdentityPacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -418,6 +436,8 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
@Override
|
@Override
|
||||||
public void onNetworkChange() {
|
public void onNetworkChange() {
|
||||||
broadcastUdpIdentityPacket();
|
broadcastUdpIdentityPacket();
|
||||||
|
mdnsDiscovery.stopListening();
|
||||||
|
mdnsDiscovery.startListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@@ -434,6 +454,8 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
Log.e("LanLink", "Exception", e);
|
Log.e("LanLink", "Exception", e);
|
||||||
}
|
}
|
||||||
|
mdnsDiscovery.stopAnnouncing();
|
||||||
|
mdnsDiscovery.stopListening();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
206
src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java
Normal file
206
src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java
Normal file
@@ -0,0 +1,206 @@
|
|||||||
|
/*
|
||||||
|
* 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.Backends.LanBackend;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.nsd.NsdManager;
|
||||||
|
import android.net.nsd.NsdServiceInfo;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class MdnsDiscovery {
|
||||||
|
|
||||||
|
static final String LOG_TAG = "MdnsDiscovery";
|
||||||
|
|
||||||
|
static final String SERVICE_TYPE = "_kdeconnect._udp";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final LanLinkProvider lanLinkProvider;
|
||||||
|
|
||||||
|
private final NsdManager mNsdManager;
|
||||||
|
private NsdManager.RegistrationListener registrationListener;
|
||||||
|
private NsdManager.DiscoveryListener discoveryListener;
|
||||||
|
|
||||||
|
public MdnsDiscovery(Context context, LanLinkProvider lanLinkProvider) {
|
||||||
|
this.context = context;
|
||||||
|
this.lanLinkProvider = lanLinkProvider;
|
||||||
|
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
|
||||||
|
}
|
||||||
|
|
||||||
|
void startListening() {
|
||||||
|
if (discoveryListener == null) {
|
||||||
|
discoveryListener = createDiscoveryListener();
|
||||||
|
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopListening() {
|
||||||
|
if (discoveryListener != null) {
|
||||||
|
mNsdManager.stopServiceDiscovery(discoveryListener);
|
||||||
|
discoveryListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopAnnouncing() {
|
||||||
|
if (registrationListener != null) {
|
||||||
|
mNsdManager.unregisterService(registrationListener);
|
||||||
|
registrationListener = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void startAnnouncing() {
|
||||||
|
if (registrationListener == null) {
|
||||||
|
registrationListener = createRegistrationListener();
|
||||||
|
NsdServiceInfo serviceInfo = createNsdServiceInfo();
|
||||||
|
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NsdManager.RegistrationListener createRegistrationListener() {
|
||||||
|
return new NsdManager.RegistrationListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
|
||||||
|
// If Android changed the service name to avoid conflicts, here we can read it.
|
||||||
|
Log.i(LOG_TAG, "Registered " + serviceInfo.getServiceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "Registration failed with: " + errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.d(LOG_TAG, "Service unregistered: " + serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public NsdServiceInfo createNsdServiceInfo() {
|
||||||
|
NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
||||||
|
|
||||||
|
InetAddress address = lanLinkProvider.udpServer.getInetAddress();
|
||||||
|
int port = lanLinkProvider.udpServer.getLocalPort();
|
||||||
|
serviceInfo.setHost(address);
|
||||||
|
serviceInfo.setPort(port);
|
||||||
|
|
||||||
|
// iOS seems to need these as a TXT records
|
||||||
|
serviceInfo.setAttribute("ip", address.toString());
|
||||||
|
serviceInfo.setAttribute("port", Integer.toString(port));
|
||||||
|
|
||||||
|
// The following fields aren't really used for anything, since we can't include enough info
|
||||||
|
// for it to be useful (namely: we can't include the device certificate).
|
||||||
|
// Each field (key + value) needs to be < 255 bytes. All the fields combined need to be < 1300 bytes.
|
||||||
|
// Also, on Android Lollipop those fields aren't resolved.
|
||||||
|
String deviceId = DeviceHelper.getDeviceId(context);
|
||||||
|
String deviceName = DeviceHelper.getDeviceName(context);
|
||||||
|
String deviceType = DeviceHelper.getDeviceType(context).toString();
|
||||||
|
String protocolVersion = Integer.toString(DeviceHelper.ProtocolVersion);
|
||||||
|
serviceInfo.setAttribute("id", deviceId);
|
||||||
|
serviceInfo.setAttribute("name", deviceName);
|
||||||
|
serviceInfo.setAttribute("type", deviceType);
|
||||||
|
serviceInfo.setAttribute("version", protocolVersion);
|
||||||
|
|
||||||
|
// Without resolving the DNS, the service name is the only info we have so it must be sufficient to identify a device.
|
||||||
|
// Also, it must be unique, otherwise it will be automatically renamed. For these reasons we use the deviceId.
|
||||||
|
serviceInfo.setServiceName(deviceId);
|
||||||
|
serviceInfo.setServiceType(SERVICE_TYPE);
|
||||||
|
|
||||||
|
Log.d(LOG_TAG, "My MDNS info: " + serviceInfo);
|
||||||
|
|
||||||
|
return serviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
NsdManager.DiscoveryListener createDiscoveryListener() {
|
||||||
|
return new NsdManager.DiscoveryListener() {
|
||||||
|
|
||||||
|
final String myId = DeviceHelper.getDeviceId(context);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStarted(String regType) {
|
||||||
|
Log.i(LOG_TAG, "Service discovery started");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.d(LOG_TAG, "Service discovered: " + serviceInfo);
|
||||||
|
|
||||||
|
String deviceId = serviceInfo.getServiceName();
|
||||||
|
|
||||||
|
if (myId.equals(deviceId)) {
|
||||||
|
Log.d(LOG_TAG, "Discovered myself, ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lanLinkProvider.visibleDevices.containsKey(deviceId)) {
|
||||||
|
Log.i(LOG_TAG, "MDNS discovered " + deviceId + " to which I'm already connected to. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
mNsdManager.resolveService(serviceInfo, createResolveListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.w(LOG_TAG, "Service lost: " + serviceInfo);
|
||||||
|
// We can't see this device via mdns. This probably means it's not reachable anymore
|
||||||
|
// but we do nothing here since we have other ways to do detect unreachable devices
|
||||||
|
// that hopefully will also trigger.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStopped(String serviceType) {
|
||||||
|
Log.i(LOG_TAG, "MDNS discovery stopped: " + serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "MDNS discovery start failed: " + errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "MDNS discovery stop failed: " + errorCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new listener instance since NsdManager wants a different listener each time you call resolveService
|
||||||
|
*/
|
||||||
|
NsdManager.ResolveListener createResolveListener() {
|
||||||
|
return new NsdManager.ResolveListener() {
|
||||||
|
@Override
|
||||||
|
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
Log.w(LOG_TAG, "MDNS error " + errorCode + " resolving service: " + serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.i(LOG_TAG, "MDNS successfully resolved " + serviceInfo);
|
||||||
|
|
||||||
|
InetAddress remoteAddress = serviceInfo.getHost();
|
||||||
|
|
||||||
|
// Let the LanLinkProvider handle the connection
|
||||||
|
lanLinkProvider.sendUdpIdentityPacket(Collections.singletonList(remoteAddress));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@@ -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")) {
|
if (hasChanges || links.size() == 1) {
|
||||||
this.name = identityPacket.getString("deviceName", this.name);
|
reloadPluginsFromSettings();
|
||||||
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;
|
|
||||||
|
|
||||||
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());
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
* 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
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.kde.kdeconnect;
|
package org.kde.kdeconnect;
|
||||||
|
|
||||||
@@ -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);
|
||||||
|
@@ -37,6 +37,9 @@ import org.kde.kdeconnect_tp.R;
|
|||||||
|
|
||||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
|
public static final String KEY_UDP_BROADCAST_ENABLED = "udp_broadcast_enabled";
|
||||||
|
public static final String KEY_APP_THEME = "theme_pref";
|
||||||
|
|
||||||
private EditTextPreference renameDevice;
|
private EditTextPreference renameDevice;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@@ -90,7 +93,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
|
|
||||||
// Theme Selector
|
// Theme Selector
|
||||||
ListPreference themeSelector = new ListPreference(context);
|
ListPreference themeSelector = new ListPreference(context);
|
||||||
themeSelector.setKey("theme_pref");
|
themeSelector.setKey(KEY_APP_THEME);
|
||||||
themeSelector.setTitle(R.string.theme_dialog_title);
|
themeSelector.setTitle(R.string.theme_dialog_title);
|
||||||
themeSelector.setDialogTitle(R.string.theme_dialog_title);
|
themeSelector.setDialogTitle(R.string.theme_dialog_title);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
@@ -168,6 +171,13 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// UDP broadcast toggle
|
||||||
|
final TwoStatePreference udpBroadcastDiscovery = new SwitchPreference(context);
|
||||||
|
udpBroadcastDiscovery.setPersistent(false);
|
||||||
|
udpBroadcastDiscovery.setDefaultValue(true);
|
||||||
|
udpBroadcastDiscovery.setKey(KEY_UDP_BROADCAST_ENABLED);
|
||||||
|
udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast);
|
||||||
|
screen.addPreference(udpBroadcastDiscovery);
|
||||||
|
|
||||||
// More settings text
|
// More settings text
|
||||||
Preference moreSettingsText = new Preference(context);
|
Preference moreSettingsText = new Preference(context);
|
||||||
|
@@ -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);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user