2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-30 21:55:10 +00:00

Revert "Failed mega refactor"

This reverts commit 8201b8cbdf028cc8946f4e6a8127a316b8cf1339.
This commit is contained in:
Albert Vaca Cintora
2020-07-18 23:58:05 +02:00
parent cf32416243
commit 826c0a854e
14 changed files with 482 additions and 478 deletions

View File

@@ -31,7 +31,7 @@ import java.util.ArrayList;
import androidx.annotation.WorkerThread;
public abstract class DeviceLink {
public abstract class BaseLink {
protected final Context context;
@@ -44,7 +44,7 @@ public abstract class DeviceLink {
private final ArrayList<PacketReceiver> receivers = new ArrayList<>();
protected PrivateKey privateKey;
protected DeviceLink(Context context, String deviceId, BaseLinkProvider linkProvider) {
protected BaseLink(Context context, String deviceId, BaseLinkProvider linkProvider) {
this.context = context;
this.linkProvider = linkProvider;
this.deviceId = deviceId;
@@ -62,6 +62,15 @@ public abstract class DeviceLink {
privateKey = key;
}
public BaseLinkProvider getLinkProvider() {
return linkProvider;
}
//The daemon will periodically destroy unpaired links if this returns false
public boolean linkShouldBeKeptAlive() {
return false;
}
public void addPacketReceiver(PacketReceiver pr) {
receivers.add(pr);
}
@@ -77,7 +86,7 @@ public abstract class DeviceLink {
}
public void disconnect() {
linkProvider.onLinkDisconnected(this);
linkProvider.connectionLost(this);
}
//TO OVERRIDE, should be sync

View File

@@ -20,6 +20,8 @@
package org.kde.kdeconnect.Backends;
import org.kde.kdeconnect.NetworkPacket;
import java.util.concurrent.CopyOnWriteArrayList;
public abstract class BaseLinkProvider {
@@ -27,47 +29,38 @@ public abstract class BaseLinkProvider {
private final CopyOnWriteArrayList<ConnectionReceiver> connectionReceivers = new CopyOnWriteArrayList<>();
public interface ConnectionReceiver {
void onOfferAdded(DeviceOffer offer);
void onOfferRemoved(String id);
void onLinkConnected(DeviceOffer offer, DeviceLink link);
void onConnectionFailed(DeviceOffer offer, String reason);
void onLinkDisconnected(DeviceLink link);
void onConnectionReceived(NetworkPacket identityPacket, BaseLink link);
void onConnectionLost(BaseLink link);
}
public void addConnectionReceiver(ConnectionReceiver cr) {
connectionReceivers.add(cr);
}
public boolean removeConnectionReceiver(ConnectionReceiver cr) { return connectionReceivers.remove(cr); }
protected void onOfferAdded(DeviceOffer offer) {
for(ConnectionReceiver cr : connectionReceivers) {
cr.onOfferAdded(offer);
}
}
protected void onOfferRemoved(String id) {
for(ConnectionReceiver cr : connectionReceivers) {
cr.onOfferRemoved(id);
}
public boolean removeConnectionReceiver(ConnectionReceiver cr) {
return connectionReceivers.remove(cr);
}
protected void onLinkConnected(DeviceOffer offer, DeviceLink link) {
//These two should be called when the provider links to a new computer
protected void connectionAccepted(NetworkPacket identityPacket, BaseLink link) {
//Log.i("KDE/LinkProvider", "connectionAccepted");
for(ConnectionReceiver cr : connectionReceivers) {
cr.onLinkConnected(offer, link);
cr.onConnectionReceived(identityPacket, link);
}
}
protected void onConnectionFailed(DeviceOffer offer, String reason) {
protected void connectionLost(BaseLink link) {
//Log.i("KDE/LinkProvider", "connectionLost");
for(ConnectionReceiver cr : connectionReceivers) {
cr.onConnectionFailed(offer, reason);
}
}
protected void onLinkDisconnected(DeviceLink link) {
for(ConnectionReceiver cr : connectionReceivers) {
cr.onLinkDisconnected(link);
cr.onConnectionLost(link);
}
}
//To override
public abstract void refresh();
public abstract void onStart();
public abstract void onStop();
public abstract void onNetworkChange();
//public abstract int getPriority();
public abstract String getName();
public abstract void connect(DeviceOffer id);
}

View File

@@ -20,13 +20,17 @@
package org.kde.kdeconnect.Backends.BluetoothBackend;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothServerSocket;
import android.bluetooth.BluetoothSocket;
import android.content.Context;
import android.os.Build;
import android.util.Log;
import org.json.JSONException;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
@@ -41,7 +45,7 @@ import java.util.UUID;
import androidx.annotation.WorkerThread;
public class BluetoothLink extends DeviceLink {
public class BluetoothLink extends BaseLink {
private final ConnectionMultiplexer connection;
private final InputStream input;
private final OutputStream output;
@@ -199,6 +203,10 @@ public class BluetoothLink extends DeviceLink {
}
}
@Override
public boolean linkShouldBeKeptAlive() {
return receivingThread.isAlive();
}
/*
public boolean isConnected() {

View File

@@ -35,7 +35,6 @@ import android.util.Log;
import org.apache.commons.io.IOUtils;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
@@ -73,9 +72,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
return;
}
visibleComputers.put(deviceId, link);
onLinkConnected(DeviceOffer.FromLegacyIdentityPacket(identityPacket), link);
connectionAccepted(identityPacket, link);
link.startListening();
if (oldLink != null) {
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
@@ -92,6 +89,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
}
}
@Override
public void onStart() {
if (bluetoothAdapter == null) {
return;
@@ -115,11 +113,13 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
new Thread(serverRunnable).start();
}
public void refresh() {
@Override
public void onNetworkChange() {
onStop();
onStart();
}
@Override
public void onStop() {
if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
return;
@@ -129,19 +129,15 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
serverRunnable.stopProcessing();
}
@Override
public String getName() {
return "BluetoothLinkProvider";
}
@Override
public void connect(DeviceOffer id) {
// TODO
}
public void disconnectedLink(BluetoothLink link, String deviceId, BluetoothDevice remoteAddress) {
sockets.remove(remoteAddress);
visibleComputers.remove(deviceId);
onLinkDisconnected(link);
connectionLost(link);
}
private class ServerRunnable implements Runnable {

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2014 Albert Vaca Cintora <albertvaka@gmail.com>
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License as
* published by the Free Software Foundation; either version 2 of
* the License or (at your option) version 3 or any later version
* accepted by the membership of KDE e.V. (or its successor approved
* by the membership of KDE e.V.), which shall act as a proxy
* defined in Section 14 of version 3 of the license.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package org.kde.kdeconnect.Backends;
import android.util.Log;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
import java.net.InetAddress;
public class DeviceOffer {
public String id;
public String name;
public Device.DeviceType type;
public int protocolVersion;
public InetAddress host;
public int port;
public static DeviceOffer FromLegacyIdentityPacket(NetworkPacket identityPacket) {
DeviceOffer ret = new DeviceOffer();
ret.id = identityPacket.getString("deviceId");
ret.protocolVersion = identityPacket.getInt("protocolVersion");
ret.name = identityPacket.getString("deviceName");
ret.type = Device.DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
return ret;
}
public BaseLinkProvider provider = null;
public void connect() {
if (provider == null) {
Log.e("AAA", "ERROR: Can't connect, provider unknown");
return;
}
provider.connect(this);
}
}

View File

@@ -24,7 +24,7 @@ import android.content.Context;
import android.util.Log;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
@@ -47,7 +47,7 @@ import javax.net.ssl.SSLSocket;
import androidx.annotation.WorkerThread;
public class LanLink extends DeviceLink {
public class LanLink extends BaseLink {
public interface LinkDisconnectedCallback {
void linkDisconnected(LanLink brokenLink);
@@ -260,4 +260,15 @@ public class LanLink extends DeviceLink {
packageReceived(np);
}
@Override
public boolean linkShouldBeKeptAlive() {
return true; //FIXME: Current implementation is broken, so for now we will keep links always established
//We keep the remotely initiated connections, since the remotes require them if they want to request
//pairing to us, or connections that are already paired.
//return (connectionSource == ConnectionStarted.Remotely);
}
}

View File

@@ -26,9 +26,8 @@ import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
@@ -90,7 +89,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
public void linkDisconnected(LanLink brokenLink) {
String deviceId = brokenLink.getDeviceId();
visibleComputers.remove(deviceId);
onLinkDisconnected(brokenLink);
connectionLost(brokenLink);
}
//They received my UDP broadcast and are connecting to me. The first thing they sned should be their identity.
@@ -165,7 +164,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}, 5 * 1000);
// Try to cause a reverse connection
refresh();
onNetworkChange();
}
}
}
@@ -263,7 +262,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
* link is operated on at a time.
* <p>
* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in
* {@link Device#addLink(NetworkPacket, DeviceLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* </p>
*
* @param identityPacket representation of remote device
@@ -285,14 +284,12 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
//Let's create the link
LanLink link = new LanLink(context, deviceId, this, socket, connectionOrigin);
visibleComputers.put(deviceId, link);
onLinkConnected(DeviceOffer.FromLegacyIdentityPacket(identityPacket), link);
connectionAccepted(identityPacket, link);
}
}
public LanLinkProvider(Context context)
{
public LanLinkProvider(Context context) {
this.context = context;
onStart();
}
private void setupUdpListener() {
@@ -416,6 +413,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}).start();
}
@Override
public void onStart() {
//Log.i("KDE/LanLinkProvider", "onStart");
if (!listening) {
@@ -430,10 +428,11 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}
@Override
public void refresh() {
public void onNetworkChange() {
broadcastUdpPacket();
}
@Override
public void onStop() {
//Log.i("KDE/LanLinkProvider", "onStop");
listening = false;
@@ -454,9 +453,4 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
return "LanLinkProvider";
}
@Override
public void connect(DeviceOffer offer) {
}
}

View File

@@ -22,7 +22,7 @@ package org.kde.kdeconnect.Backends.LoopbackBackend;
import android.content.Context;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
@@ -30,7 +30,7 @@ import org.kde.kdeconnect.NetworkPacket;
import androidx.annotation.WorkerThread;
public class LoopbackLink extends DeviceLink {
public class LoopbackLink extends BaseLink {
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
super(context, "loopback", linkProvider);

View File

@@ -23,9 +23,6 @@ package org.kde.kdeconnect.Backends.LoopbackBackend;
import android.content.Context;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.NetworkPacket;
public class LoopbackLinkProvider extends BaseLinkProvider {
@@ -37,20 +34,25 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
}
@Override
public void connect(DeviceOffer offer) {
onLinkConnected(offer, new LoopbackLink(context, this));
public void onStart() {
onNetworkChange();
}
@Override
public void refresh() {
DeviceOffer offer = new DeviceOffer();
offer.id = DeviceHelper.getDeviceId(context);
offer.name = DeviceHelper.getDeviceName(context);
offer.type = DeviceHelper.getDeviceType(context);
offer.protocolVersion = DeviceHelper.ProtocolVersion;
onOfferAdded(offer);
public void onStop() {
}
@Override
public void onNetworkChange() {
NetworkPacket np = NetworkPacket.createIdentityPacket(context);
connectionAccepted(np, new LoopbackLink(context, this));
}
/*
@Override
public int getPriority() {
return 0;
}
*/
@Override
public String getName() {
return "LoopbackLinkProvider";

View File

@@ -25,7 +25,7 @@ import android.content.Context;
import android.util.Log;
import org.json.JSONObject;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
@@ -45,7 +45,7 @@ import java.nio.channels.NotYetConnectedException;
import javax.net.ssl.SSLSocket;
public class MulticastLink extends DeviceLink {
public class MulticastLink extends BaseLink {
static final String LOG_TAG = "MulticastLink";
@@ -57,6 +57,11 @@ public class MulticastLink extends DeviceLink {
Locally, Remotely
}
private ConnectionStarted connectionSource; // If the other device sent me a broadcast,
// I should not close the connection with it
// because it's probably trying to find me and
// potentially ask for pairing.
private volatile SSLSocket socket = null;
private final LinkDisconnectedCallback callback;
@@ -72,11 +77,13 @@ public class MulticastLink extends DeviceLink {
}
//Returns the old socket
public SSLSocket reset(final SSLSocket newSocket) throws IOException {
public SSLSocket reset(final SSLSocket newSocket, ConnectionStarted connectionSource) throws IOException {
SSLSocket oldSocket = socket;
socket = newSocket;
this.connectionSource = connectionSource;
if (oldSocket != null) {
oldSocket.close(); //This should cancel the readThread
}
@@ -115,10 +122,10 @@ public class MulticastLink extends DeviceLink {
return oldSocket;
}
public MulticastLink(Context context, String deviceId, MulticastLinkProvider linkProvider, SSLSocket socket) throws IOException {
public MulticastLink(Context context, String deviceId, MulticastLinkProvider linkProvider, SSLSocket socket, ConnectionStarted connectionSource) throws IOException {
super(context, deviceId, linkProvider);
callback = linkProvider;
reset(socket);
reset(socket, connectionSource);
}
@@ -248,4 +255,14 @@ public class MulticastLink extends DeviceLink {
packageReceived(np);
}
@Override
public boolean linkShouldBeKeptAlive() {
return true; //FIXME: Current implementation is broken, so for now we will keep links always established
//We keep the remotely initiated connections, since the remotes require them if they want to request
//pairing to us, or connections that are already paired.
//return (connectionSource == ConnectionStarted.Remotely);
}
}

View File

@@ -24,36 +24,40 @@ package org.kde.kdeconnect.Backends.MulticastBackend;
import android.content.Context;
import android.content.SharedPreferences;
import android.net.Network;
import android.net.nsd.NsdManager;
import android.net.nsd.NsdManager.RegistrationListener;
import android.net.nsd.NsdManager.ResolveListener;
import android.net.nsd.NsdServiceInfo;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.os.Build;
import android.util.Base64;
import android.util.Log;
import androidx.annotation.RequiresApi;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.json.JSONException;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.NetworkPacket;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.UnknownHostException;
import java.nio.charset.StandardCharsets;
import java.security.cert.Certificate;
import java.util.ArrayList;
import java.util.HashMap;
import javax.net.SocketFactory;
import javax.net.ssl.SSLSocket;
/**
@@ -65,13 +69,13 @@ import javax.net.ssl.SSLSocket;
*/
public class MulticastLinkProvider extends BaseLinkProvider implements MulticastLink.LinkDisconnectedCallback {
HashMap<InetAddress, DeviceOffer> offers = new HashMap<>();
static final String LOG_TAG = "MulticastLink";
static final String SERVICE_TYPE = "_kdeconnect._tcp";
private NsdManager mNsdManager;
private RegistrationListener mRegistrationListener;
private boolean mServiceRegistered = false;
private final static int MIN_PORT = 1716;
private final static int MAX_PORT = 1764;
@@ -85,31 +89,31 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
private boolean listening = false;
// To prevent infinte loop between Android < IceCream because both device can only broadcast identity package but cannot connect via TCP
private final ArrayList<InetAddress> reverseConnectionBlackList = new ArrayList<>();
@Override // SocketClosedCallback
public void linkDisconnected(MulticastLink brokenLink) {
String deviceId = brokenLink.getDeviceId();
visibleComputers.remove(deviceId);
onLinkDisconnected(brokenLink);
connectionLost(brokenLink);
}
private InetAddress getDeviceIpAddress() {
WifiManager wifi = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiManager.MulticastLock lock = wifi.createMulticastLock("jmdns-multicast-lock");
lock.setReferenceCounted(true);
lock.acquire();
InetAddress result = null;
try {
// figure out our wifi address, otherwise bail
WifiInfo wifiinfo = wifi.getConnectionInfo();
int intaddr = wifiinfo.getIpAddress();
byte[] byteaddr = new byte[] { (byte) (intaddr & 0xff), (byte) (intaddr >> 8 & 0xff),
(byte) (intaddr >> 16 & 0xff), (byte) (intaddr >> 24 & 0xff) };
return InetAddress.getByAddress(byteaddr);
} catch (UnknownHostException e) {
e.printStackTrace();
return null;
// They received my mDNS broadcast and are connecting to me. The first thing they send should be their identity.
private void tcpPacketReceived(Socket socket) {
writeIdentity(socket);
NetworkPacket otherIdentity = readIdentity(socket);
if (!otherIdentity.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) {
Log.e(LOG_TAG, "Expecting an identity package instead of " + otherIdentity.getType());
return;
}
Log.i(LOG_TAG, "Identity package received from a TCP connection from " + otherIdentity.getString("deviceName"));
identityPacketReceived(otherIdentity, socket, MulticastLink.ConnectionStarted.Locally);
}
private void configureSocket(Socket socket) {
try {
socket.setKeepAlive(true);
@@ -118,34 +122,58 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
}
}
/**
* Called when a socket is connected.
* Called when a new 'identity' packet is received. Those are passed here by
* {@link #tcpPacketReceived(Socket)}
* <p>
* If the remote device should be connected, this calls {@link #addLink}.
* Otherwise, if there was an Exception, we unpair from that device.
* </p>
*
* @param identityPacket identity of a remote device
* @param socket a new Socket, which should be used to receive packets from the remote device
* @param connectionStarted which side started this connection
*/
private void doTheSslDance(DeviceOffer deviceOffer, final Socket socket, final MulticastLink.ConnectionStarted connectionStarted) {
private void identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final MulticastLink.ConnectionStarted connectionStarted) {
String deviceId = deviceOffer.id;
String myId = DeviceHelper.getDeviceId(context);
final String deviceId = identityPacket.getString("deviceId");
if (deviceId.equals(myId)) {
Log.e(LOG_TAG, "Somehow I'm connected to myself, ignoring. This should not happen.");
return;
}
// If I'm the TCP server I will be the SSL client and viceversa.
final boolean clientMode = (connectionStarted == MulticastLink.ConnectionStarted.Locally);
// Do the SSL handshake
try {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
boolean isDeviceTrusted = preferences.getBoolean(deviceId, false);
Log.i(LOG_TAG, "Starting SSL handshake with " + deviceOffer.name + " trusted:" + isDeviceTrusted);
if (isDeviceTrusted && !SslHelper.isCertificateStored(context, deviceId)) {
//Device paired with and old version, we can't use it as we lack the certificate
BackgroundService.RunCommand(context, service -> {
Device device = service.getDevice(deviceId);
if (device == null) return;
device.unpair();
//Retry as unpaired
identityPacketReceived(identityPacket, socket, connectionStarted);
});
}
Log.i(LOG_TAG, "Starting SSL handshake with " + identityPacket.getString("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];
createLink(deviceOffer, sslsocket);
identityPacket.set("certificate", Base64.encodeToString(certificate.getEncoded(), 0));
Log.i(LOG_TAG, "Handshake as " + mode + " successful with " + identityPacket.getString("deviceName") + " secured with " + event.getCipherSuite());
addLink(identityPacket, sslsocket, connectionStarted);
} catch (Exception e) {
String mode = clientMode ? "client" : "server";
Log.e(LOG_TAG, "Handshake as " + mode + " failed with " + deviceOffer.name, e);
Log.e(LOG_TAG, "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
BackgroundService.RunCommand(context, service -> {
Device device = service.getDevice(deviceId);
if (device == null) return;
@@ -160,7 +188,7 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
sslsocket.startHandshake();
}
} catch (Exception e) {
Log.e(LOG_TAG, "Handshake failed with " + deviceOffer.name, e);
Log.e(LOG_TAG, "Handshake failed with " + identityPacket.getString("deviceName"), e);
//String[] ciphers = sslsocket.getSupportedCipherSuites();
//for (String cipher : ciphers) {
@@ -179,7 +207,7 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
* link is operated on at a time.
* <p>
* Without synchronization, the call to {@link SslHelper#parseCertificate(byte[])} in
* {@link Device#addLink(NetworkPacket, DeviceLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* {@link Device#addLink(NetworkPacket, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* </p>
*
* @param identityPacket representation of remote device
@@ -187,50 +215,53 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
* @param connectionOrigin which side started this connection
* @throws IOException if an exception is thrown by {@link MulticastLink#reset(SSLSocket, MulticastLink.ConnectionStarted)}
*/
private void createLink(DeviceOffer offer, SSLSocket socket) throws IOException {
String deviceId = offer.id;
private void addLink(final NetworkPacket identityPacket, SSLSocket socket, MulticastLink.ConnectionStarted connectionOrigin) throws IOException {
String deviceId = identityPacket.getString("deviceId");
MulticastLink currentLink = visibleComputers.get(deviceId);
if (currentLink != null) {
//Update old link
Log.i(LOG_TAG, "Reusing same link for device " + deviceId);
final Socket oldSocket = currentLink.reset(socket);
final Socket oldSocket = currentLink.reset(socket, connectionOrigin);
//Log.e(LOG_TAG, "Replacing socket. old: "+ oldSocket.hashCode() + " - new: "+ socket.hashCode());
} else {
Log.i(LOG_TAG, "Creating a new link for device " + deviceId);
//Let's create the link
MulticastLink link = new MulticastLink(context, deviceId, this, socket);
MulticastLink link = new MulticastLink(context, deviceId, this, socket, connectionOrigin);
visibleComputers.put(deviceId, link);
onLinkConnected(offer, link);
connectionAccepted(identityPacket, link);
}
}
private NsdServiceInfo createServiceInfoForTcpServer(InetAddress address, int port) {
NsdServiceInfo serviceInfo = new NsdServiceInfo();
// The name is subject to change based on conflicts
// with other services advertised on the same network.
String name = DeviceHelper.getDeviceName(context);
serviceInfo.setAttribute("name", name);
serviceInfo.setAttribute("id", DeviceHelper.getDeviceId(context));
serviceInfo.setAttribute("type", DeviceHelper.getDeviceType(context).toString());
serviceInfo.setAttribute("version", Integer.toString(DeviceHelper.ProtocolVersion));
serviceInfo.setAttribute("ip", address.toString());
// It might be nice to add the capabilities in the mDNS advertisement too, but without
// some kind of encoding that is too large for the TXT record
serviceInfo.setServiceName("KDE Connect on " + name);
serviceInfo.setServiceType(SERVICE_TYPE);
serviceInfo.setPort(port);
serviceInfo.setHost(address);
return serviceInfo;
}
public MulticastLinkProvider(Context context) {
this.context = context;
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
}
private void writeIdentity(Socket socket) {
try {
OutputStream out = socket.getOutputStream();
NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context);
out.write(myIdentity.serialize().getBytes());
out.flush();
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to get stream from socket", e);
return;
} catch (JSONException e) {
Log.e(LOG_TAG, "Unable to deserialize JSON", e);
return;
}
}
private NetworkPacket readIdentity(Socket socket) {
try {
BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String message = reader.readLine();
return NetworkPacket.unserialize(message);
//Log.e("TcpListener","Received TCP package: "+networkPacket.serialize());
} catch (Exception e) {
Log.e(LOG_TAG, "Exception while receiving TCP packet", e);
return null;
}
}
private void setupTcpListener() {
@@ -245,21 +276,14 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
try {
Socket socket = tcpServer.accept();
configureSocket(socket);
InetAddress remoteAddress = socket.getInetAddress();
DeviceOffer offer = offers.get(remoteAddress);
//if (offer == null) {
// Log.e(LOG_TAG, "Received a connection from an unknown device "+ remoteAddress.toString() + ", ignoring!");
// socket.close();
// return;
// //offer = (DeviceOffer)(offers.values().toArray()[0]);
//}
doTheSslDance(offer, socket, MulticastLink.ConnectionStarted.Remotely);
tcpPacketReceived(socket);
} catch (Exception e) {
Log.e(LOG_TAG, "TcpReceive exception", e);
}
}
Log.w("TcpListener", "Stopping TCP listener");
}).start();
}
static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException {
@@ -281,37 +305,78 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
throw new RuntimeException("This should not be reachable");
}
NsdManager.RegistrationListener mRegistrationListener = new NsdManager.RegistrationListener() {
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
@Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
// Save the service name. Android may have changed it in order to
// resolve a conflict, so update the name you initially requested
// with the name Android actually used.
Log.i(LOG_TAG, "Registered " + NsdServiceInfo.getServiceName());
@Override
public void onServiceRegistered(NsdServiceInfo NsdServiceInfo) {
// Save the service name. Android may have changed it in order to
// resolve a conflict, so update the name you initially requested
// with the name Android actually used.
Log.i(LOG_TAG, "Registered " + NsdServiceInfo.getServiceName());
}
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
Log.e(LOG_TAG, "Registration failed");
}
@Override
public void onServiceUnregistered(NsdServiceInfo arg0) {
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
Log.w(LOG_TAG, "Service unregistered: " + arg0);
}
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Put debugging code here to determine why.
Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode);
}
};
}
@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
public void initializeNsdManager(RegistrationListener registrationListener) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
return;
}
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
Log.e(LOG_TAG, "Registration failed");
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
try {
mNsdManager.unregisterService(registrationListener);
} catch (java.lang.IllegalArgumentException e) {
// not yet registered, but it's fine.
}
NsdServiceInfo serviceInfo = new NsdServiceInfo();
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
// The name is subject to change based on conflicts
// with other services advertised on the same network.
NetworkPacket myIdentity = NetworkPacket.createIdentityPacket(context);
String did = myIdentity.getString("deviceID");
String name = myIdentity.getString("deviceName");
InetAddress addr = this.tcpServer.getInetAddress();
int port = this.tcpServer.getLocalPort();
Log.e(LOG_TAG, "Service unregistered: " + serviceInfo);
offers.remove(serviceInfo.getHost());
}
// These cause the requirement for api level 21.
serviceInfo.setAttribute("name", myIdentity.getString("deviceName"));
serviceInfo.setAttribute("id", myIdentity.getString("deviceId"));
serviceInfo.setAttribute("type", myIdentity.getString("deviceType"));
serviceInfo.setAttribute("version", myIdentity.getString("protocolVersion"));
@Override
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Unregistration failed. Put debugging code here to determine why.
Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode);
}
};
// It might be nice to add the capabilities in the mDNS advertisement too, but without
// some kind of encoding that is too large for the TXT record
serviceInfo.setServiceName("KDE Connect on " + myIdentity.getString("deviceName"));
serviceInfo.setServiceType(SERVICE_TYPE);
serviceInfo.setHost(addr);
serviceInfo.setPort(port);
//Log.d("KDE/Lan", "service: " + serviceInfo.toString());
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public ResolveListener createResolveListener() {
@@ -322,134 +387,120 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
Log.e(LOG_TAG, "Could not resolve service: " + serviceInfo);
}
String getString(NsdServiceInfo serviceInfo, String key) {
byte[] raw = serviceInfo.getAttributes().get(key);
if (raw == null) {
return null;
}
return new String(raw, StandardCharsets.UTF_8);
}
@Override
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.e(LOG_TAG, "Successfully resolved " + serviceInfo);
String id = getString(serviceInfo, "id");
if (id == null) {
Log.e(LOG_TAG, "Id not found");
return;
}
if (id.equals(DeviceHelper.getDeviceId(context))) {
Log.e(LOG_TAG, "Ignoring myself " + serviceInfo);
return;
}
String name = getString(serviceInfo, "name");
if (name == null) {
Log.e(LOG_TAG, "Name not found");
return;
}
String type_s = getString(serviceInfo, "type");
if (type_s == null) {
Log.e(LOG_TAG, "Device type not found");
return;
}
Device.DeviceType type = Device.DeviceType.FromString(type_s);
String version_s = getString(serviceInfo, "version");
if (version_s == null) {
Log.e(LOG_TAG, "Protocol version not found");
return;
}
int protocolVersion = Integer.parseInt(version_s);
Log.i(LOG_TAG, "Successfully resolved " + serviceInfo);
InetAddress hostname = serviceInfo.getHost();
int remotePort = serviceInfo.getPort();
DeviceOffer offer = new DeviceOffer();
offer.id = id;
offer.name = name;
offer.type = type;
offer.protocolVersion = protocolVersion;
offer.host = hostname;
offer.port = remotePort;
offer.provider = MulticastLinkProvider.this;
SocketFactory socketFactory = SocketFactory.getDefault();
Socket socket;
try {
socket = socketFactory.createSocket(hostname, remotePort);
} catch (IOException e) {
Log.e(LOG_TAG, "Unable to open socket to mDNS remote: " + serviceInfo, e);
return;
}
offers.put(hostname, offer);
onOfferAdded(offer);
writeIdentity(socket);
NetworkPacket otherIdentity = readIdentity(socket);
try {
Log.i(LOG_TAG, "Got identity: " + otherIdentity.serialize());
} catch (JSONException e) {
Log.e(LOG_TAG, "Got identity, but unable to serialize!", e);
return;
}
}
};
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public void initializeDiscoveryListener() {
// Instantiate a new DiscoveryListener
NsdManager.DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() {
NsdManager.DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() {
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(String regType) {
Log.d(LOG_TAG, "Service discovery started");
}
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(String regType) {
Log.e(LOG_TAG, "Service discovery started");
}
@Override
public void onServiceFound(NsdServiceInfo service) {
// A service was found! Do something with it.
Log.d(LOG_TAG, "Service discovery success" + service);
mNsdManager.resolveService(service, createResolveListener());
}
@Override
public void onServiceFound(NsdServiceInfo service) {
// A service was found! Do something with it.
Log.e(LOG_TAG, "Service discovery success " + service);
mNsdManager.resolveService(service, createResolveListener());
}
@Override
public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available.
// Internal bookkeeping code goes here.
Log.e(LOG_TAG, "service lost: " + service);
}
@Override
public void onServiceLost(NsdServiceInfo service) {
// When the network service is no longer available.
// Internal bookkeeping code goes here.
Log.e(LOG_TAG, "service lost: " + service);
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.i(LOG_TAG, "Discovery stopped: " + serviceType);
listening = false;
}
@Override
public void onDiscoveryStopped(String serviceType) {
Log.e(LOG_TAG, "Discovery stopped: " + serviceType);
listening = false;
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
@Override
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
}
@Override
public void refresh() {
public void onStart() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
Log.i(LOG_TAG, "MulticastBackend not supported on devices older thab Lollipop");
return;
}
if (!listening) {
listening = true;
// Need to set up TCP before setting up mDNS because we need to know the TCP listening
// address and port
setupTcpListener();
initializeRegistrationListener();
initializeNsdManager(mRegistrationListener);
initializeDiscoveryListener();
}
}
@Override
public void onNetworkChange() {
onStop();
onStart();
}
public synchronized void onStart() {
Log.e(LOG_TAG,"ON_START");
if (!listening) {
Log.e(LOG_TAG,"ON_START doing things");
listening = true;
// We set the tcp server port on the service info, so we need to create it beforehand
setupTcpListener();
NsdServiceInfo serviceInfo = createServiceInfoForTcpServer( getDeviceIpAddress(), this.tcpServer.getLocalPort());
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, mRegistrationListener);
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
@Override
public void onStop() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// This backend does not work on older Android versions
return;
}
}
public synchronized void onStop() {
/*Log.e(LOG_TAG,"ON_STOP");
if (listening) {
Log.e(LOG_TAG,"ON_STOP doing things");
mNsdManager.stopServiceDiscovery(discoveryListener);
mNsdManager.unregisterService(mRegistrationListener);
try { tcpServer.close(); } catch (IOException ignore) { }
listening = false;
}*/
mNsdManager.unregisterService(mRegistrationListener);
tcpServer.close();
listening = false;
}
@Override
@@ -457,29 +508,4 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
return "MulticastLinkProvider";
}
@Override
public void connect(DeviceOffer offer) {
try {
Socket socket = new Socket(offer.host,offer.port);
doTheSslDance(offer, socket, MulticastLink.ConnectionStarted.Locally);
} catch (IOException e) {
onConnectionFailed(offer, e.getLocalizedMessage());
e.printStackTrace();
}
}
/*
void trustDevice() {
if (identityPacket.has("certificate")) {
String certificateString = identityPacket.getString("certificate");
try {
byte[] certificateBytes = Base64.decode(certificateString, 0);
certificate = SslHelper.parseCertificate(certificateBytes);
Log.i("KDE/Device", "Got certificate ");
} catch (Exception e) {
Log.e("KDE/Device", "Error getting certificate", e);
}
}
*/
}

View File

@@ -39,9 +39,8 @@ import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
import org.kde.kdeconnect.Backends.MulticastBackend.MulticastLinkProvider;
import org.kde.kdeconnect.Helpers.NotificationHelper;
@@ -84,7 +83,6 @@ public class BackgroundService extends Service {
private final ArrayList<BaseLinkProvider> linkProviders = new ArrayList<>();
private final ConcurrentHashMap<String, Device> devices = new ConcurrentHashMap<>();
private final ConcurrentHashMap<String, DeviceOffer> deviceOffers = new ConcurrentHashMap<>();
private final HashSet<Object> discoveryModeAcquisitions = new HashSet<>();
@@ -102,6 +100,19 @@ public class BackgroundService extends Service {
return wasEmpty;
}
private void releaseDiscoveryMode(Object key) {
boolean removed = discoveryModeAcquisitions.remove(key);
//Log.e("releaseDiscoveryMode",key.getClass().getName() +" ["+discoveryModeAcquisitions.size()+"]");
if (removed && discoveryModeAcquisitions.isEmpty()) {
cleanDevices();
}
}
private boolean isInDiscoveryMode() {
//return !discoveryModeAcquisitions.isEmpty();
return true; // Keep it always on for now
}
private final Device.PairingCallback devicePairingCallback = new Device.PairingCallback() {
@Override
public void incomingRequest() {
@@ -152,7 +163,7 @@ public class BackgroundService extends Service {
}
private void registerLinkProviders() {
//linkProviders.add(new LanLinkProvider(this));
linkProviders.add(new LanLinkProvider(this));
linkProviders.add(new MulticastLinkProvider(this));
// linkProviders.add(new LoopbackLinkProvider(this));
// linkProviders.add(new BluetoothLinkProvider(this));
@@ -166,55 +177,46 @@ public class BackgroundService extends Service {
return devices.get(id);
}
private void cleanDevices() {
new Thread(() -> {
for (Device d : devices.values()) {
if (!d.isPaired() && !d.isPairRequested() && !d.isPairRequestedByPeer() && !d.deviceShouldBeKeptAlive()) {
d.disconnect();
}
}
}).start();
}
private final BaseLinkProvider.ConnectionReceiver deviceListener = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onOfferAdded(DeviceOffer offer) {
deviceOffers.put(offer.id, offer);
public void onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) {
String deviceId = identityPacket.getString("deviceId");
// TEST
Log.e("KDE/BackgroundService", "offer added: " + offer.id);
Device device = devices.get(offer.id);
if (device == null) {
device = new Device(BackgroundService.this, offer, Device.PairStatus.NotPaired);
devices.put(offer.id, device);
Log.e("KDE/BackgroundService", "device stored: " + offer.id);
}
// TEST
offer.connect();
onDeviceListChanged();
}
@Override
public void onOfferRemoved(String id) {
Log.e("KDE/BackgroundService", "offer removed: " + id);
deviceOffers.remove(id);
if (devices.get(id) != null && !devices.get(id).isReachable() && !devices.get(id).isPaired()) {
devices.remove(id);
}
onDeviceListChanged();
}
@Override
public void onLinkConnected(DeviceOffer offer, DeviceLink link) {
String deviceId = link.getDeviceId();
Log.e("KDE/BackgroundService", "device connected: " + deviceId);
Device device = devices.get(deviceId);
if (device == null) {
device = new Device(BackgroundService.this, offer, Device.PairStatus.Paired);
devices.put(link.getDeviceId(), device);
if (device != null) {
Log.i("KDE/BackgroundService", "addLink, known device: " + deviceId);
device.addLink(identityPacket, link);
} else {
Log.i("KDE/BackgroundService", "addLink,unknown device: " + deviceId);
device = new Device(BackgroundService.this, identityPacket, link);
if (device.isPaired() || device.isPairRequested() || device.isPairRequestedByPeer()
|| link.linkShouldBeKeptAlive()
|| isInDiscoveryMode()) {
devices.put(deviceId, device);
device.addPairingCallback(devicePairingCallback);
} else {
device.disconnect();
}
}
device.addLink(link);
onDeviceListChanged();
}
@Override
public void onLinkDisconnected(DeviceLink link) {
Device d = devices.get(link);
public void onConnectionLost(BaseLink link) {
Device d = devices.get(link.getDeviceId());
Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId());
if (d != null) {
d.removeLink(link);
@@ -228,12 +230,6 @@ public class BackgroundService extends Service {
}
onDeviceListChanged();
}
@Override
public void onConnectionFailed(DeviceOffer offer, String reason) {
Log.e("KDE/BackgroundService", "device connect failed: " + reason);
}
};
public ConcurrentHashMap<String, Device> getDevices() {
@@ -241,15 +237,8 @@ public class BackgroundService extends Service {
}
public void onNetworkChange() {
for (DeviceOffer d : deviceOffers.values()) {
Log.e("YESOFFER" , "OFFER " + d.name);
}
for (Device d : devices.values()) {
Log.e("NOTOFFER" , "DEVICE " + d.getName());
}
for (BaseLinkProvider a : linkProviders) {
a.refresh();
a.onNetworkChange();
}
}
@@ -301,7 +290,7 @@ public class BackgroundService extends Service {
addConnectionListener(deviceListener);
for (BaseLinkProvider a : linkProviders) {
a.refresh();
a.onStart();
}
}
@@ -413,10 +402,9 @@ public class BackgroundService extends Service {
@Override
public void onDestroy() {
stopForeground(true);
/*
for (BaseLinkProvider a : linkProviders) {
a.onStop();
}*/
}
super.onDestroy();
}

View File

@@ -41,11 +41,11 @@ import androidx.core.content.ContextCompat;
import org.apache.commons.collections4.MultiValuedMap;
import org.apache.commons.collections4.multimap.ArrayListValuedHashMap;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.MainActivity;
@@ -66,7 +66,7 @@ import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
public class Device implements DeviceLink.PacketReceiver {
public class Device implements BaseLink.PacketReceiver {
private final Context context;
@@ -82,7 +82,7 @@ public class Device implements DeviceLink.PacketReceiver {
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
private final Map<String, BasePairingHandler> pairingHandlers = new HashMap<>();
private final CopyOnWriteArrayList<DeviceLink> links = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
private DevicePacketQueue packetQueue;
private List<String> supportedPlugins = new ArrayList<>();
@@ -170,28 +170,21 @@ public class Device implements DeviceLink.PacketReceiver {
//reloadPluginsFromSettings();
}
//Remembered trusted device, we need to wait for a incoming devicelink to communicate
Device(Context context, DeviceOffer deviceOffer, PairStatus paired) {
settings = context.getSharedPreferences(deviceOffer.id, Context.MODE_PRIVATE);
//Device known via an incoming connection sent to us via a devicelink, we know everything but we don't trust it yet
Device(Context context, NetworkPacket np, BaseLink dl) {
//Log.e("Device","Constructor B");
Log.e("AAAAA", "Adding device "+deviceOffer.name);
this.context = context;
this.deviceId = deviceOffer.id;
this.name = deviceOffer.name;
this.pairStatus = paired;
this.protocolVersion = deviceOffer.protocolVersion;
this.deviceType = deviceOffer.type;
this.deviceId = np.getString("deviceId");
this.name = context.getString(R.string.unknown_device); //We read it in addLink
this.pairStatus = PairStatus.NotPaired;
this.protocolVersion = 0;
this.deviceType = DeviceType.Computer;
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", this.name);
editor.putString("deviceType", this.deviceType.toString());
editor.apply();
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
//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();
addLink(np, dl);
}
public String getName() {
@@ -441,7 +434,7 @@ public class Device implements DeviceLink.PacketReceiver {
return !links.isEmpty();
}
public void addLink(DeviceLink link) {
public void addLink(NetworkPacket identityPacket, BaseLink link) {
if (links.isEmpty()) {
packetQueue = new DevicePacketQueue(this);
}
@@ -449,6 +442,31 @@ public class Device implements DeviceLink.PacketReceiver {
links.add(link);
link.addPacketReceiver(this);
this.protocolVersion = identityPacket.getInt("protocolVersion");
if (identityPacket.has("deviceName")) {
this.name = identityPacket.getString("deviceName", this.name);
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", this.name);
editor.apply();
}
if (identityPacket.has("deviceType")) {
this.deviceType = DeviceType.FromString(identityPacket.getString("deviceType", "desktop"));
}
if (identityPacket.has("certificate")) {
String certificateString = identityPacket.getString("certificate");
try {
byte[] certificateBytes = Base64.decode(certificateString, 0);
certificate = SslHelper.parseCertificate(certificateBytes);
Log.i("KDE/Device", "Got certificate ");
} catch (Exception e) {
Log.e("KDE/Device", "Error getting certificate", e);
}
}
try {
SharedPreferences globalSettings = PreferenceManager.getDefaultSharedPreferences(context);
@@ -459,7 +477,7 @@ public class Device implements DeviceLink.PacketReceiver {
Log.e("KDE/Device", "Exception reading our own private key", e); //Should not happen
}
Log.i("KDE/Device", "addLink " + link + " -> " + getName() + " active links: " + links.size());
Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
if (!pairingHandlers.containsKey(link.getName())) {
BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() {
@@ -490,10 +508,11 @@ public class Device implements DeviceLink.PacketReceiver {
pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback));
}
Set<String> outgoingCapabilities = new HashSet<>();
Set<String> incomingCapabilities = new HashSet<>();
Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
if (!incomingCapabilities.isEmpty() && !outgoingCapabilities.isEmpty()) {
if (incomingCapabilities != null && outgoingCapabilities != null) {
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
} else {
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
@@ -504,12 +523,12 @@ public class Device implements DeviceLink.PacketReceiver {
}
public void removeLink(DeviceLink link) {
public void removeLink(BaseLink link) {
//FilesHelper.LogOpenFileCount();
/* Remove pairing handler corresponding to that link too if it was the only link*/
boolean linkPresent = false;
for (DeviceLink bl : links) {
for (BaseLink bl : links) {
if (bl.getName().equals(link.getName())) {
linkPresent = true;
break;
@@ -521,7 +540,7 @@ public class Device implements DeviceLink.PacketReceiver {
link.removePacketReceiver(this);
links.remove(link);
Log.i("KDE/Device", "removeLink: " + link + " -> " + getName() + " active links: " + links.size());
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
if (links.isEmpty()) {
reloadPluginsFromSettings();
if (packetQueue != null) {
@@ -670,7 +689,7 @@ public class Device implements DeviceLink.PacketReceiver {
* @param np the packet to send
* @param callback a callback that can receive realtime updates
* @return true if the packet was sent ok, false otherwise
* @see DeviceLink#sendPacket(NetworkPacket, SendPacketStatusCallback)
* @see BaseLink#sendPacket(NetworkPacket, SendPacketStatusCallback)
*/
@WorkerThread
public boolean sendPacketBlocking(final NetworkPacket np, final SendPacketStatusCallback callback) {
@@ -684,7 +703,7 @@ public class Device implements DeviceLink.PacketReceiver {
boolean success = false;
//Make a copy to avoid concurrent modification exception if the original list changes
for (final DeviceLink link : links) {
for (final BaseLink link : links) {
if (link == null)
continue; //Since we made a copy, maybe somebody destroyed the link in the meanwhile
success = link.sendPacket(np, callback);
@@ -861,10 +880,27 @@ public class Device implements DeviceLink.PacketReceiver {
}
public void disconnect() {
for (DeviceLink link : links) {
for (BaseLink link : links) {
link.disconnect();
}
}
public boolean deviceShouldBeKeptAlive() {
SharedPreferences preferences = context.getSharedPreferences("trusted_devices", Context.MODE_PRIVATE);
if (preferences.contains(getDeviceId())) {
//Log.e("DeviceShouldBeKeptAlive", "because it's a paired device");
return true; //Already paired
}
for (BaseLink l : links) {
if (l.linkShouldBeKeptAlive()) {
return true;
}
}
return false;
}
public List<String> getSupportedPlugins() {
return supportedPlugins;
}

View File

@@ -51,11 +51,11 @@ import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat;
import androidx.fragment.app.FragmentManager;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.DeviceOffer;
import org.kde.kdeconnect.BackgroundService;
import org.kde.kdeconnect.Helpers.VideoUrlsHelper;
import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.Plugins.SystemvolumePlugin.SystemvolumeFragment;
import org.kde.kdeconnect.UserInterface.ThemeUtil;
import org.kde.kdeconnect_tp.R;
@@ -237,32 +237,15 @@ public class MprisActivity extends AppCompatActivity {
}
private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onOfferAdded(DeviceOffer offer) {
}
@Override
public void onOfferRemoved(String id) {
}
@Override
public void onLinkConnected(DeviceOffer offer, DeviceLink link) {
public void onConnectionReceived(NetworkPacket identityPacket, BaseLink link) {
connectToPlugin(null);
}
@Override
public void onConnectionFailed(DeviceOffer offer, String reason) {
public void onConnectionLost(BaseLink link) {
}
@Override
public void onLinkDisconnected(DeviceLink link) {
}
};
@Override