2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-29 13:17:43 +00:00

Failed mega refactor

This commit is contained in:
Albert Vaca Cintora 2020-07-18 23:51:45 +02:00
parent d8749d8f85
commit cf32416243
14 changed files with 482 additions and 486 deletions

View File

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

View File

@ -20,17 +20,13 @@
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.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
import org.kde.kdeconnect.Backends.BasePairingHandler;
import org.kde.kdeconnect.Device;
import org.kde.kdeconnect.NetworkPacket;
@ -45,7 +41,7 @@ import java.util.UUID;
import androidx.annotation.WorkerThread;
public class BluetoothLink extends BaseLink {
public class BluetoothLink extends DeviceLink {
private final ConnectionMultiplexer connection;
private final InputStream input;
private final OutputStream output;
@ -203,10 +199,6 @@ public class BluetoothLink extends BaseLink {
}
}
@Override
public boolean linkShouldBeKeptAlive() {
return receivingThread.isAlive();
}
/*
public boolean isConnected() {

View File

@ -35,6 +35,7 @@ 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;
@ -72,7 +73,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
return;
}
visibleComputers.put(deviceId, link);
connectionAccepted(identityPacket, link);
onLinkConnected(DeviceOffer.FromLegacyIdentityPacket(identityPacket), link);
link.startListening();
if (oldLink != null) {
Log.i("BluetoothLinkProvider", "Removing old connection to same device");
@ -89,7 +92,6 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
}
}
@Override
public void onStart() {
if (bluetoothAdapter == null) {
return;
@ -113,13 +115,11 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
new Thread(serverRunnable).start();
}
@Override
public void onNetworkChange() {
public void refresh() {
onStop();
onStart();
}
@Override
public void onStop() {
if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
return;
@ -129,15 +129,19 @@ 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);
connectionLost(link);
onLinkDisconnected(link);
}
private class ServerRunnable implements Runnable {

View File

@ -31,7 +31,7 @@ import java.util.ArrayList;
import androidx.annotation.WorkerThread;
public abstract class BaseLink {
public abstract class DeviceLink {
protected final Context context;
@ -44,7 +44,7 @@ public abstract class BaseLink {
private final ArrayList<PacketReceiver> receivers = new ArrayList<>();
protected PrivateKey privateKey;
protected BaseLink(Context context, String deviceId, BaseLinkProvider linkProvider) {
protected DeviceLink(Context context, String deviceId, BaseLinkProvider linkProvider) {
this.context = context;
this.linkProvider = linkProvider;
this.deviceId = deviceId;
@ -62,15 +62,6 @@ public abstract class BaseLink {
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);
}
@ -86,7 +77,7 @@ public abstract class BaseLink {
}
public void disconnect() {
linkProvider.connectionLost(this);
linkProvider.onLinkDisconnected(this);
}
//TO OVERRIDE, should be sync

View File

@ -0,0 +1,59 @@
/*
* 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.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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 BaseLink {
public class LanLink extends DeviceLink {
public interface LinkDisconnectedCallback {
void linkDisconnected(LanLink brokenLink);
@ -260,15 +260,4 @@ public class LanLink extends BaseLink {
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,8 +26,9 @@ import android.preference.PreferenceManager;
import android.util.Base64;
import android.util.Log;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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;
@ -89,7 +90,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
public void linkDisconnected(LanLink brokenLink) {
String deviceId = brokenLink.getDeviceId();
visibleComputers.remove(deviceId);
connectionLost(brokenLink);
onLinkDisconnected(brokenLink);
}
//They received my UDP broadcast and are connecting to me. The first thing they sned should be their identity.
@ -164,7 +165,7 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}, 5 * 1000);
// Try to cause a reverse connection
onNetworkChange();
refresh();
}
}
}
@ -262,7 +263,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, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* {@link Device#addLink(NetworkPacket, DeviceLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* </p>
*
* @param identityPacket representation of remote device
@ -284,12 +285,14 @@ 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);
connectionAccepted(identityPacket, link);
onLinkConnected(DeviceOffer.FromLegacyIdentityPacket(identityPacket), link);
}
}
public LanLinkProvider(Context context) {
public LanLinkProvider(Context context)
{
this.context = context;
onStart();
}
private void setupUdpListener() {
@ -413,7 +416,6 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}).start();
}
@Override
public void onStart() {
//Log.i("KDE/LanLinkProvider", "onStart");
if (!listening) {
@ -428,11 +430,10 @@ public class LanLinkProvider extends BaseLinkProvider implements LanLink.LinkDis
}
@Override
public void onNetworkChange() {
public void refresh() {
broadcastUdpPacket();
}
@Override
public void onStop() {
//Log.i("KDE/LanLinkProvider", "onStop");
listening = false;
@ -453,4 +454,9 @@ 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.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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 BaseLink {
public class LoopbackLink extends DeviceLink {
public LoopbackLink(Context context, BaseLinkProvider linkProvider) {
super(context, "loopback", linkProvider);

View File

@ -23,6 +23,9 @@ 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 {
@ -34,25 +37,20 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
}
@Override
public void onStart() {
onNetworkChange();
public void connect(DeviceOffer offer) {
onLinkConnected(offer, new LoopbackLink(context, this));
}
@Override
public void onStop() {
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);
}
@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.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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 BaseLink {
public class MulticastLink extends DeviceLink {
static final String LOG_TAG = "MulticastLink";
@ -57,11 +57,6 @@ public class MulticastLink extends BaseLink {
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;
@ -77,13 +72,11 @@ public class MulticastLink extends BaseLink {
}
//Returns the old socket
public SSLSocket reset(final SSLSocket newSocket, ConnectionStarted connectionSource) throws IOException {
public SSLSocket reset(final SSLSocket newSocket) throws IOException {
SSLSocket oldSocket = socket;
socket = newSocket;
this.connectionSource = connectionSource;
if (oldSocket != null) {
oldSocket.close(); //This should cancel the readThread
}
@ -122,10 +115,10 @@ public class MulticastLink extends BaseLink {
return oldSocket;
}
public MulticastLink(Context context, String deviceId, MulticastLinkProvider linkProvider, SSLSocket socket, ConnectionStarted connectionSource) throws IOException {
public MulticastLink(Context context, String deviceId, MulticastLinkProvider linkProvider, SSLSocket socket) throws IOException {
super(context, deviceId, linkProvider);
callback = linkProvider;
reset(socket, connectionSource);
reset(socket);
}
@ -255,14 +248,4 @@ public class MulticastLink extends BaseLink {
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,40 +24,36 @@ 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.json.JSONException;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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;
/**
@ -69,13 +65,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;
@ -89,31 +85,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);
connectionLost(brokenLink);
onLinkDisconnected(brokenLink);
}
// 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;
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;
}
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);
@ -122,58 +118,34 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
}
}
/**
* 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>
* Called when a socket is connected.
*
* @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 identityPacketReceived(final NetworkPacket identityPacket, final Socket socket, final MulticastLink.ConnectionStarted connectionStarted) {
private void doTheSslDance(DeviceOffer deviceOffer, final Socket socket, final MulticastLink.ConnectionStarted connectionStarted) {
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;
}
String deviceId = deviceOffer.id;
// 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);
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);
Log.i(LOG_TAG, "Starting SSL handshake with " + deviceOffer.name + " 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];
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);
createLink(deviceOffer, sslsocket);
} catch (Exception e) {
Log.e(LOG_TAG, "Handshake as " + mode + " failed with " + identityPacket.getString("deviceName"), e);
String mode = clientMode ? "client" : "server";
Log.e(LOG_TAG, "Handshake as " + mode + " failed with " + deviceOffer.name, e);
BackgroundService.RunCommand(context, service -> {
Device device = service.getDevice(deviceId);
if (device == null) return;
@ -188,7 +160,7 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
sslsocket.startHandshake();
}
} catch (Exception e) {
Log.e(LOG_TAG, "Handshake failed with " + identityPacket.getString("deviceName"), e);
Log.e(LOG_TAG, "Handshake failed with " + deviceOffer.name, e);
//String[] ciphers = sslsocket.getSupportedCipherSuites();
//for (String cipher : ciphers) {
@ -207,7 +179,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, BaseLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* {@link Device#addLink(NetworkPacket, DeviceLink)} crashes on some devices running Oreo 8.1 (SDK level 27).
* </p>
*
* @param identityPacket representation of remote device
@ -215,53 +187,50 @@ 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 addLink(final NetworkPacket identityPacket, SSLSocket socket, MulticastLink.ConnectionStarted connectionOrigin) throws IOException {
String deviceId = identityPacket.getString("deviceId");
private void createLink(DeviceOffer offer, SSLSocket socket) throws IOException {
String deviceId = offer.id;
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, connectionOrigin);
final Socket oldSocket = currentLink.reset(socket);
//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, connectionOrigin);
MulticastLink link = new MulticastLink(context, deviceId, this, socket);
visibleComputers.put(deviceId, link);
connectionAccepted(identityPacket, link);
onLinkConnected(offer, 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;
}
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;
}
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
}
private void setupTcpListener() {
@ -276,14 +245,21 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
try {
Socket socket = tcpServer.accept();
configureSocket(socket);
tcpPacketReceived(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);
} catch (Exception e) {
Log.e(LOG_TAG, "TcpReceive exception", e);
}
}
Log.w("TcpListener", "Stopping TCP listener");
}).start();
}
static ServerSocket openServerSocketOnFreePort(int minPort) throws IOException {
@ -305,78 +281,37 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
throw new RuntimeException("This should not be reachable");
}
@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
public void initializeRegistrationListener() {
mRegistrationListener = new NsdManager.RegistrationListener() {
NsdManager.RegistrationListener 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 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 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());
}
mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
try {
mNsdManager.unregisterService(registrationListener);
} catch (java.lang.IllegalArgumentException e) {
// not yet registered, but it's fine.
@Override
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
// Registration failed! Put debugging code here to determine why.
Log.e(LOG_TAG, "Registration failed");
}
NsdServiceInfo serviceInfo = new NsdServiceInfo();
// 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();
@Override
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
// Service has been unregistered. This only happens when you call
// NsdManager.unregisterService() and pass in this listener.
// 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"));
Log.e(LOG_TAG, "Service unregistered: " + serviceInfo);
offers.remove(serviceInfo.getHost());
}
// 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);
}
@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(api = Build.VERSION_CODES.JELLY_BEAN)
public ResolveListener createResolveListener() {
@ -387,120 +322,134 @@ public class MulticastLinkProvider extends BaseLinkProvider implements Multicast
Log.e(LOG_TAG, "Could not resolve service: " + serviceInfo);
}
@Override
String getString(NsdServiceInfo serviceInfo, String key) {
byte[] raw = serviceInfo.getAttributes().get(key);
if (raw == null) {
return null;
}
return new String(raw, StandardCharsets.UTF_8);
}
public void onServiceResolved(NsdServiceInfo serviceInfo) {
Log.i(LOG_TAG, "Successfully resolved " + 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);
InetAddress hostname = serviceInfo.getHost();
int remotePort = serviceInfo.getPort();
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;
}
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;
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;
}
offers.put(hostname, offer);
onOfferAdded(offer);
}
};
}
@RequiresApi(api = Build.VERSION_CODES.JELLY_BEAN)
public void initializeDiscoveryListener() {
// Instantiate a new 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");
}
NsdManager.DiscoveryListener discoveryListener = new NsdManager.DiscoveryListener() {
@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 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 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);
}
};
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
}
@Override
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;
// Called as soon as service discovery begins.
@Override
public void onDiscoveryStarted(String regType) {
Log.e(LOG_TAG, "Service discovery started");
}
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 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 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 onStopDiscoveryFailed(String serviceType, int errorCode) {
Log.e(LOG_TAG, "Discovery failed: Error code:" + errorCode);
mNsdManager.stopServiceDiscovery(this);
}
};
@Override
public void onNetworkChange() {
public void refresh() {
onStop();
onStart();
}
@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 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);
}
}
mNsdManager.unregisterService(mRegistrationListener);
tcpServer.close();
listening = false;
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;
}*/
}
@Override
@ -508,4 +457,29 @@ 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,8 +39,9 @@ import android.util.Log;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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;
@ -83,6 +84,7 @@ 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<>();
@ -100,19 +102,6 @@ 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() {
@ -163,7 +152,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));
@ -177,46 +166,55 @@ 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 onConnectionReceived(final NetworkPacket identityPacket, final BaseLink link) {
public void onOfferAdded(DeviceOffer offer) {
deviceOffers.put(offer.id, offer);
String deviceId = identityPacket.getString("deviceId");
Device device = devices.get(deviceId);
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();
}
// 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 onConnectionLost(BaseLink link) {
Device d = devices.get(link.getDeviceId());
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);
}
device.addLink(link);
onDeviceListChanged();
}
@Override
public void onLinkDisconnected(DeviceLink link) {
Device d = devices.get(link);
Log.i("KDE/onConnectionLost", "removeLink, deviceId: " + link.getDeviceId());
if (d != null) {
d.removeLink(link);
@ -230,6 +228,12 @@ 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() {
@ -237,8 +241,15 @@ 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.onNetworkChange();
a.refresh();
}
}
@ -290,7 +301,7 @@ public class BackgroundService extends Service {
addConnectionListener(deviceListener);
for (BaseLinkProvider a : linkProviders) {
a.onStart();
a.refresh();
}
}
@ -402,9 +413,10 @@ 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.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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 BaseLink.PacketReceiver {
public class Device implements DeviceLink.PacketReceiver {
private final Context context;
@ -82,7 +82,7 @@ public class Device implements BaseLink.PacketReceiver {
private final CopyOnWriteArrayList<PairingCallback> pairingCallback = new CopyOnWriteArrayList<>();
private final Map<String, BasePairingHandler> pairingHandlers = new HashMap<>();
private final CopyOnWriteArrayList<BaseLink> links = new CopyOnWriteArrayList<>();
private final CopyOnWriteArrayList<DeviceLink> links = new CopyOnWriteArrayList<>();
private DevicePacketQueue packetQueue;
private List<String> supportedPlugins = new ArrayList<>();
@ -170,21 +170,28 @@ public class Device implements BaseLink.PacketReceiver {
//reloadPluginsFromSettings();
}
//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");
//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);
Log.e("AAAAA", "Adding device "+deviceOffer.name);
this.context = context;
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;
this.deviceId = deviceOffer.id;
this.name = deviceOffer.name;
this.pairStatus = paired;
this.protocolVersion = deviceOffer.protocolVersion;
this.deviceType = deviceOffer.type;
settings = context.getSharedPreferences(deviceId, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = settings.edit();
editor.putString("deviceName", this.name);
editor.putString("deviceType", this.deviceType.toString());
editor.apply();
addLink(np, dl);
//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();
}
public String getName() {
@ -434,7 +441,7 @@ public class Device implements BaseLink.PacketReceiver {
return !links.isEmpty();
}
public void addLink(NetworkPacket identityPacket, BaseLink link) {
public void addLink(DeviceLink link) {
if (links.isEmpty()) {
packetQueue = new DevicePacketQueue(this);
}
@ -442,31 +449,6 @@ public class Device implements BaseLink.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);
@ -477,7 +459,7 @@ public class Device implements BaseLink.PacketReceiver {
Log.e("KDE/Device", "Exception reading our own private key", e); //Should not happen
}
Log.i("KDE/Device", "addLink " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
Log.i("KDE/Device", "addLink " + link + " -> " + getName() + " active links: " + links.size());
if (!pairingHandlers.containsKey(link.getName())) {
BasePairingHandler.PairingHandlerCallback callback = new BasePairingHandler.PairingHandlerCallback() {
@ -508,11 +490,10 @@ public class Device implements BaseLink.PacketReceiver {
pairingHandlers.put(link.getName(), link.getPairingHandler(this, callback));
}
Set<String> outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null);
Set<String> incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null);
Set<String> outgoingCapabilities = new HashSet<>();
Set<String> incomingCapabilities = new HashSet<>();
if (incomingCapabilities != null && outgoingCapabilities != null) {
if (!incomingCapabilities.isEmpty() && !outgoingCapabilities.isEmpty()) {
supportedPlugins = new Vector<>(PluginFactory.pluginsForCapabilities(incomingCapabilities, outgoingCapabilities));
} else {
supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins());
@ -523,12 +504,12 @@ public class Device implements BaseLink.PacketReceiver {
}
public void removeLink(BaseLink link) {
public void removeLink(DeviceLink link) {
//FilesHelper.LogOpenFileCount();
/* Remove pairing handler corresponding to that link too if it was the only link*/
boolean linkPresent = false;
for (BaseLink bl : links) {
for (DeviceLink bl : links) {
if (bl.getName().equals(link.getName())) {
linkPresent = true;
break;
@ -540,7 +521,7 @@ public class Device implements BaseLink.PacketReceiver {
link.removePacketReceiver(this);
links.remove(link);
Log.i("KDE/Device", "removeLink: " + link.getLinkProvider().getName() + " -> " + getName() + " active links: " + links.size());
Log.i("KDE/Device", "removeLink: " + link + " -> " + getName() + " active links: " + links.size());
if (links.isEmpty()) {
reloadPluginsFromSettings();
if (packetQueue != null) {
@ -689,7 +670,7 @@ public class Device implements BaseLink.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 BaseLink#sendPacket(NetworkPacket, SendPacketStatusCallback)
* @see DeviceLink#sendPacket(NetworkPacket, SendPacketStatusCallback)
*/
@WorkerThread
public boolean sendPacketBlocking(final NetworkPacket np, final SendPacketStatusCallback callback) {
@ -703,7 +684,7 @@ public class Device implements BaseLink.PacketReceiver {
boolean success = false;
//Make a copy to avoid concurrent modification exception if the original list changes
for (final BaseLink link : links) {
for (final DeviceLink link : links) {
if (link == null)
continue; //Since we made a copy, maybe somebody destroyed the link in the meanwhile
success = link.sendPacket(np, callback);
@ -880,27 +861,10 @@ public class Device implements BaseLink.PacketReceiver {
}
public void disconnect() {
for (BaseLink link : links) {
for (DeviceLink 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.BaseLink;
import org.kde.kdeconnect.Backends.DeviceLink;
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,15 +237,32 @@ public class MprisActivity extends AppCompatActivity {
}
private final BaseLinkProvider.ConnectionReceiver connectionReceiver = new BaseLinkProvider.ConnectionReceiver() {
@Override
public void onConnectionReceived(NetworkPacket identityPacket, BaseLink link) {
public void onOfferAdded(DeviceOffer offer) {
}
@Override
public void onOfferRemoved(String id) {
}
@Override
public void onLinkConnected(DeviceOffer offer, DeviceLink link) {
connectToPlugin(null);
}
@Override
public void onConnectionLost(BaseLink link) {
public void onConnectionFailed(DeviceOffer offer, String reason) {
}
@Override
public void onLinkDisconnected(DeviceLink link) {
}
};
@Override