diff --git a/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java b/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java index 2ba72be2..e3a24bf0 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/BaseLinkProvider.java @@ -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 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); } diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java index 27e9f3f5..a8e2a18c 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLink.java @@ -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() { diff --git a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java index 9c1076af..e81fe544 100644 --- a/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/BluetoothBackend/BluetoothLinkProvider.java @@ -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 { diff --git a/src/org/kde/kdeconnect/Backends/BaseLink.java b/src/org/kde/kdeconnect/Backends/DeviceLink.java similarity index 86% rename from src/org/kde/kdeconnect/Backends/BaseLink.java rename to src/org/kde/kdeconnect/Backends/DeviceLink.java index bf9507d5..c039fbed 100644 --- a/src/org/kde/kdeconnect/Backends/BaseLink.java +++ b/src/org/kde/kdeconnect/Backends/DeviceLink.java @@ -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 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 diff --git a/src/org/kde/kdeconnect/Backends/DeviceOffer.java b/src/org/kde/kdeconnect/Backends/DeviceOffer.java new file mode 100644 index 00000000..c724592d --- /dev/null +++ b/src/org/kde/kdeconnect/Backends/DeviceOffer.java @@ -0,0 +1,59 @@ +/* + * Copyright 2014 Albert Vaca Cintora + * + * 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 . +*/ + +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); + } + +} diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java index 5c264766..88c3e9d2 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLink.java @@ -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); - - } } diff --git a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java index ebf45f48..192f1984 100644 --- a/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LanBackend/LanLinkProvider.java @@ -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. *

* 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). *

* * @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) { + + } + } diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java index 9ee0a753..4ef6ccac 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLink.java @@ -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); diff --git a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java index 09559466..82d9f2ea 100644 --- a/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/LoopbackBackend/LoopbackLinkProvider.java @@ -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"; diff --git a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java index 0d3048de..7195e07a 100644 --- a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java +++ b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLink.java @@ -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); - - } } diff --git a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java index 4f8e7149..e2bed163 100644 --- a/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java +++ b/src/org/kde/kdeconnect/Backends/MulticastBackend/MulticastLinkProvider.java @@ -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 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 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)} - *

- * If the remote device should be connected, this calls {@link #addLink}. - * Otherwise, if there was an Exception, we unpair from that device. - *

+ * 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. *

* 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). *

* * @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); + } + } + */ } diff --git a/src/org/kde/kdeconnect/BackgroundService.java b/src/org/kde/kdeconnect/BackgroundService.java index effdb06a..359e081b 100644 --- a/src/org/kde/kdeconnect/BackgroundService.java +++ b/src/org/kde/kdeconnect/BackgroundService.java @@ -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 linkProviders = new ArrayList<>(); private final ConcurrentHashMap devices = new ConcurrentHashMap<>(); + private final ConcurrentHashMap deviceOffers = new ConcurrentHashMap<>(); private final HashSet 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 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(); } diff --git a/src/org/kde/kdeconnect/Device.java b/src/org/kde/kdeconnect/Device.java index c810d177..bbbd7d9a 100644 --- a/src/org/kde/kdeconnect/Device.java +++ b/src/org/kde/kdeconnect/Device.java @@ -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 = new CopyOnWriteArrayList<>(); private final Map pairingHandlers = new HashMap<>(); - private final CopyOnWriteArrayList links = new CopyOnWriteArrayList<>(); + private final CopyOnWriteArrayList links = new CopyOnWriteArrayList<>(); private DevicePacketQueue packetQueue; private List 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 outgoingCapabilities = identityPacket.getStringSet("outgoingCapabilities", null); - Set incomingCapabilities = identityPacket.getStringSet("incomingCapabilities", null); + Set outgoingCapabilities = new HashSet<>(); + Set 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 getSupportedPlugins() { return supportedPlugins; } diff --git a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java index d5ed3b6d..03632104 100644 --- a/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java +++ b/src/org/kde/kdeconnect/Plugins/MprisPlugin/MprisActivity.java @@ -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