mirror of
https://github.com/KDE/kdeconnect-android
synced 2025-08-22 09:58:08 +00:00
Add MDNS discovery
Uses Android's `NsdManager` to announce a `_kdeconnect._udp` service using MDNS. This is done in addition to sending UDP broadcasts. When we detect a device this way, we send a UDP identity packet to it (identical to the ones we broadcast but sent to a single device). I also added a toggle in settings to disable the UDP broadcasts, so we can test MDNS by itself.
This commit is contained in:
parent
b545257fa7
commit
8f8a09a99a
@ -547,4 +547,6 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
|
|||||||
|
|
||||||
<string name="plugin_stats">Plugin stats</string>
|
<string name="plugin_stats">Plugin stats</string>
|
||||||
|
|
||||||
|
<string name="enable_udp_broadcast">Enable UDP device discovery</string>
|
||||||
|
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -25,6 +25,7 @@ import org.kde.kdeconnect.Helpers.TrustedNetworkHelper;
|
|||||||
import org.kde.kdeconnect.KdeConnect;
|
import org.kde.kdeconnect.KdeConnect;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
import org.kde.kdeconnect.UserInterface.CustomDevicesActivity;
|
||||||
|
import org.kde.kdeconnect.UserInterface.SettingsFragment;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
import java.io.BufferedReader;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -58,9 +59,9 @@ import kotlin.text.Charsets;
|
|||||||
*/
|
*/
|
||||||
public class LanLinkProvider extends BaseLinkProvider {
|
public class LanLinkProvider extends BaseLinkProvider {
|
||||||
|
|
||||||
private final static int UDP_PORT = 1716;
|
final static int UDP_PORT = 1716;
|
||||||
private final static int MIN_PORT = 1716;
|
final static int MIN_PORT = 1716;
|
||||||
private final static int MAX_PORT = 1764;
|
final static int MAX_PORT = 1764;
|
||||||
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
|
final static int PAYLOAD_TRANSFER_MIN_PORT = 1739;
|
||||||
|
|
||||||
final static int MAX_UDP_PACKET_SIZE = 1024 * 512;
|
final static int MAX_UDP_PACKET_SIZE = 1024 * 512;
|
||||||
@ -69,13 +70,15 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
|
||||||
private final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
|
final HashMap<String, LanLink> visibleDevices = new HashMap<>(); //Links by device id
|
||||||
|
|
||||||
final ConcurrentHashMap<String, Long> lastConnectionTime = new ConcurrentHashMap<>();
|
final ConcurrentHashMap<String, Long> lastConnectionTime = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
private ServerSocket tcpServer;
|
private ServerSocket tcpServer;
|
||||||
private DatagramSocket udpServer;
|
private DatagramSocket udpServer;
|
||||||
|
|
||||||
|
private MdnsDiscovery mdnsDiscovery;
|
||||||
|
|
||||||
private long lastBroadcast = 0;
|
private long lastBroadcast = 0;
|
||||||
private final static long delayBetweenBroadcasts = 200;
|
private final static long delayBetweenBroadcasts = 200;
|
||||||
|
|
||||||
@ -265,6 +268,7 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
|
|
||||||
public LanLinkProvider(Context context) {
|
public LanLinkProvider(Context context) {
|
||||||
this.context = context;
|
this.context = context;
|
||||||
|
this.mdnsDiscovery = new MdnsDiscovery(context, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setupUdpListener() {
|
private void setupUdpListener() {
|
||||||
@ -352,11 +356,11 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void broadcastUdpIdentityPacket() {
|
private void broadcastUdpIdentityPacket() {
|
||||||
if (System.currentTimeMillis() < lastBroadcast + delayBetweenBroadcasts) {
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
|
||||||
Log.i("LanLinkProvider", "broadcastUdpPacket: relax cowboy");
|
if (!preferences.getBoolean(SettingsFragment.KEY_UDP_BROADCAST_ENABLED, true)) {
|
||||||
|
Log.i("LanLinkProvider", "UDP broadcast is disabled in settings. Skipping.");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
lastBroadcast = System.currentTimeMillis();
|
|
||||||
|
|
||||||
ThreadHelper.execute(() -> {
|
ThreadHelper.execute(() -> {
|
||||||
List<String> ipStringList = CustomDevicesActivity
|
List<String> ipStringList = CustomDevicesActivity
|
||||||
@ -436,19 +440,32 @@ public class LanLinkProvider extends BaseLinkProvider {
|
|||||||
setupUdpListener();
|
setupUdpListener();
|
||||||
setupTcpListener();
|
setupTcpListener();
|
||||||
|
|
||||||
|
mdnsDiscovery.startDiscovering();
|
||||||
|
mdnsDiscovery.startAnnouncing();
|
||||||
|
|
||||||
broadcastUdpIdentityPacket();
|
broadcastUdpIdentityPacket();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onNetworkChange() {
|
public void onNetworkChange() {
|
||||||
|
if (System.currentTimeMillis() < lastBroadcast + delayBetweenBroadcasts) {
|
||||||
|
Log.i("LanLinkProvider", "onNetworkChange: relax cowboy");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
lastBroadcast = System.currentTimeMillis();
|
||||||
|
|
||||||
broadcastUdpIdentityPacket();
|
broadcastUdpIdentityPacket();
|
||||||
|
mdnsDiscovery.stopDiscovering();
|
||||||
|
mdnsDiscovery.startDiscovering();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
//Log.i("KDE/LanLinkProvider", "onStop");
|
//Log.i("KDE/LanLinkProvider", "onStop");
|
||||||
listening = false;
|
listening = false;
|
||||||
|
mdnsDiscovery.stopAnnouncing();
|
||||||
|
mdnsDiscovery.stopDiscovering();
|
||||||
try {
|
try {
|
||||||
tcpServer.close();
|
tcpServer.close();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
224
src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java
Normal file
224
src/org/kde/kdeconnect/Backends/LanBackend/MdnsDiscovery.java
Normal file
@ -0,0 +1,224 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Backends.LanBackend;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.nsd.NsdManager;
|
||||||
|
import android.net.nsd.NsdServiceInfo;
|
||||||
|
import android.net.wifi.WifiManager;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import org.kde.kdeconnect.Helpers.DeviceHelper;
|
||||||
|
|
||||||
|
import java.net.InetAddress;
|
||||||
|
import java.util.Collections;
|
||||||
|
|
||||||
|
public class MdnsDiscovery {
|
||||||
|
|
||||||
|
static final String LOG_TAG = "MdnsDiscovery";
|
||||||
|
|
||||||
|
static final String SERVICE_TYPE = "_kdeconnect._udp";
|
||||||
|
|
||||||
|
private final Context context;
|
||||||
|
|
||||||
|
private final LanLinkProvider lanLinkProvider;
|
||||||
|
|
||||||
|
private final NsdManager mNsdManager;
|
||||||
|
private NsdManager.RegistrationListener registrationListener;
|
||||||
|
private NsdManager.DiscoveryListener discoveryListener;
|
||||||
|
|
||||||
|
private WifiManager.MulticastLock multicastLock;
|
||||||
|
|
||||||
|
private NsdResolveQueue mNsdResolveQueue;
|
||||||
|
|
||||||
|
public MdnsDiscovery(Context context, LanLinkProvider lanLinkProvider) {
|
||||||
|
this.context = context;
|
||||||
|
this.lanLinkProvider = lanLinkProvider;
|
||||||
|
this.mNsdManager = (NsdManager) context.getSystemService(Context.NSD_SERVICE);
|
||||||
|
this.mNsdResolveQueue = new NsdResolveQueue(this.mNsdManager);
|
||||||
|
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
|
||||||
|
multicastLock = wifiManager.createMulticastLock("kdeConnectMdnsMulticastLock");
|
||||||
|
}
|
||||||
|
|
||||||
|
void startDiscovering() {
|
||||||
|
if (discoveryListener == null) {
|
||||||
|
multicastLock.acquire();
|
||||||
|
discoveryListener = createDiscoveryListener();
|
||||||
|
mNsdManager.discoverServices(SERVICE_TYPE, NsdManager.PROTOCOL_DNS_SD, discoveryListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopDiscovering() {
|
||||||
|
try {
|
||||||
|
if (discoveryListener != null) {
|
||||||
|
mNsdManager.stopServiceDiscovery(discoveryListener);
|
||||||
|
multicastLock.release();
|
||||||
|
}
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
// Ignore "listener not registered" exception
|
||||||
|
}
|
||||||
|
discoveryListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void stopAnnouncing() {
|
||||||
|
try {
|
||||||
|
if (registrationListener != null) {
|
||||||
|
mNsdManager.unregisterService(registrationListener);
|
||||||
|
}
|
||||||
|
} catch(IllegalArgumentException e) {
|
||||||
|
// Ignore "listener not registered" exception
|
||||||
|
}
|
||||||
|
registrationListener = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
void startAnnouncing() {
|
||||||
|
if (registrationListener == null) {
|
||||||
|
NsdServiceInfo serviceInfo;
|
||||||
|
try {
|
||||||
|
serviceInfo = createNsdServiceInfo();
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
Log.w(LOG_TAG, "Couldn't start announcing via MDNS: " + e.getMessage());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
registrationListener = createRegistrationListener();
|
||||||
|
mNsdManager.registerService(serviceInfo, NsdManager.PROTOCOL_DNS_SD, registrationListener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NsdManager.RegistrationListener createRegistrationListener() {
|
||||||
|
return new NsdManager.RegistrationListener() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceRegistered(NsdServiceInfo serviceInfo) {
|
||||||
|
// If Android changed the service name to avoid conflicts, here we can read it.
|
||||||
|
Log.i(LOG_TAG, "Registered " + serviceInfo.getServiceName());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onRegistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "Registration failed with: " + errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceUnregistered(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.d(LOG_TAG, "Service unregistered: " + serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onUnregistrationFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "Unregister of " + serviceInfo + " failed with: " + errorCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
public NsdServiceInfo createNsdServiceInfo() throws IllegalAccessException {
|
||||||
|
NsdServiceInfo serviceInfo = new NsdServiceInfo();
|
||||||
|
|
||||||
|
String deviceId = DeviceHelper.getDeviceId(context);
|
||||||
|
// Without resolving the DNS, the service name is the only info we have so it must be sufficient to identify a device.
|
||||||
|
// Also, it must be unique, otherwise it will be automatically renamed. For these reasons we use the deviceId.
|
||||||
|
serviceInfo.setServiceName(deviceId);
|
||||||
|
serviceInfo.setServiceType(SERVICE_TYPE);
|
||||||
|
serviceInfo.setPort(LanLinkProvider.UDP_PORT);
|
||||||
|
|
||||||
|
// The following fields aren't really used for anything, since we can't include enough info
|
||||||
|
// for it to be useful (namely: we can't include the device certificate).
|
||||||
|
// Each field (key + value) needs to be < 255 bytes. All the fields combined need to be < 1300 bytes.
|
||||||
|
// Also, on Android Lollipop those fields aren't resolved.
|
||||||
|
String deviceName = DeviceHelper.getDeviceName(context);
|
||||||
|
String deviceType = DeviceHelper.getDeviceType(context).toString();
|
||||||
|
String protocolVersion = Integer.toString(DeviceHelper.ProtocolVersion);
|
||||||
|
serviceInfo.setAttribute("id", deviceId);
|
||||||
|
serviceInfo.setAttribute("name", deviceName);
|
||||||
|
serviceInfo.setAttribute("type", deviceType);
|
||||||
|
serviceInfo.setAttribute("protocol", protocolVersion);
|
||||||
|
|
||||||
|
Log.i(LOG_TAG, "My MDNS info: " + serviceInfo);
|
||||||
|
|
||||||
|
return serviceInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
NsdManager.DiscoveryListener createDiscoveryListener() {
|
||||||
|
return new NsdManager.DiscoveryListener() {
|
||||||
|
|
||||||
|
final String myId = DeviceHelper.getDeviceId(context);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStarted(String serviceType) {
|
||||||
|
Log.i(LOG_TAG, "Service discovery started: " + serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceFound(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.d(LOG_TAG, "Service discovered: " + serviceInfo);
|
||||||
|
|
||||||
|
String deviceId = serviceInfo.getServiceName();
|
||||||
|
|
||||||
|
if (myId.equals(deviceId)) {
|
||||||
|
Log.d(LOG_TAG, "Discovered myself, ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lanLinkProvider.visibleDevices.containsKey(deviceId)) {
|
||||||
|
Log.i(LOG_TAG, "MDNS discovered " + deviceId + " to which I'm already connected to. Ignoring.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use a queue because only one service can be resolved at
|
||||||
|
// a time, otherwise we get error 3 (already active) in onResolveFailed.
|
||||||
|
mNsdResolveQueue.resolveOrEnqueue(serviceInfo, createResolveListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceLost(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.w(LOG_TAG, "Service lost: " + serviceInfo);
|
||||||
|
// We can't see this device via mdns. This probably means it's not reachable anymore
|
||||||
|
// but we do nothing here since we have other ways to do detect unreachable devices
|
||||||
|
// that hopefully will also trigger.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDiscoveryStopped(String serviceType) {
|
||||||
|
Log.i(LOG_TAG, "MDNS discovery stopped: " + serviceType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStartDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "MDNS discovery start failed: " + errorCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onStopDiscoveryFailed(String serviceType, int errorCode) {
|
||||||
|
Log.e(LOG_TAG, "MDNS discovery stop failed: " + errorCode);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a new listener instance since NsdManager wants a different listener each time you call resolveService
|
||||||
|
*/
|
||||||
|
NsdManager.ResolveListener createResolveListener() {
|
||||||
|
return new NsdManager.ResolveListener() {
|
||||||
|
@Override
|
||||||
|
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
Log.w(LOG_TAG, "MDNS error " + errorCode + " resolving service: " + serviceInfo);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||||||
|
Log.i(LOG_TAG, "MDNS successfully resolved " + serviceInfo);
|
||||||
|
|
||||||
|
// Let the LanLinkProvider handle the connection
|
||||||
|
InetAddress remoteAddress = serviceInfo.getHost();
|
||||||
|
lanLinkProvider.sendUdpIdentityPacket(Collections.singletonList(remoteAddress));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,91 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
|
||||||
|
package org.kde.kdeconnect.Backends.LanBackend;
|
||||||
|
|
||||||
|
import android.net.nsd.NsdManager;
|
||||||
|
import android.net.nsd.NsdServiceInfo;
|
||||||
|
import android.util.Log;
|
||||||
|
|
||||||
|
import androidx.annotation.NonNull;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
|
||||||
|
public class NsdResolveQueue {
|
||||||
|
|
||||||
|
static final String LOG_TAG = "NsdResolveQueue";
|
||||||
|
|
||||||
|
final @NonNull NsdManager mNsdManager;
|
||||||
|
|
||||||
|
private final Object mLock = new Object();
|
||||||
|
private final LinkedList<PendingResolve> mResolveRequests = new LinkedList<>();
|
||||||
|
|
||||||
|
public NsdResolveQueue(NsdManager nsdManager) {
|
||||||
|
this.mNsdManager = nsdManager;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class PendingResolve {
|
||||||
|
final @NonNull NsdServiceInfo serviceInfo;
|
||||||
|
final @NonNull NsdManager.ResolveListener listener;
|
||||||
|
|
||||||
|
private PendingResolve(@NonNull NsdServiceInfo serviceInfo, @NonNull NsdManager.ResolveListener listener) {
|
||||||
|
this.serviceInfo = serviceInfo;
|
||||||
|
this.listener = listener;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void resolveOrEnqueue(@NonNull NsdServiceInfo serviceInfo, @NonNull NsdManager.ResolveListener listener) {
|
||||||
|
synchronized (mLock) {
|
||||||
|
for (PendingResolve existing : mResolveRequests) {
|
||||||
|
if (serviceInfo.getServiceName().equals(existing.serviceInfo.getServiceName())) {
|
||||||
|
Log.i(LOG_TAG, "Not enqueuing a new resolve request for the same service: " + serviceInfo.getServiceName());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mResolveRequests.addLast(new PendingResolve(serviceInfo, new ListenerWrapper(listener)));
|
||||||
|
|
||||||
|
if (mResolveRequests.size() == 1) {
|
||||||
|
resolveNextRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ListenerWrapper implements NsdManager.ResolveListener {
|
||||||
|
private final @NonNull NsdManager.ResolveListener mListener;
|
||||||
|
|
||||||
|
private ListenerWrapper(@NonNull NsdManager.ResolveListener listener) {
|
||||||
|
mListener = listener;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onResolveFailed(NsdServiceInfo serviceInfo, int errorCode) {
|
||||||
|
mListener.onResolveFailed(serviceInfo, errorCode);
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
mResolveRequests.pop();
|
||||||
|
resolveNextRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onServiceResolved(NsdServiceInfo serviceInfo) {
|
||||||
|
mListener.onServiceResolved(serviceInfo);
|
||||||
|
|
||||||
|
synchronized (mLock) {
|
||||||
|
mResolveRequests.pop();
|
||||||
|
resolveNextRequest();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void resolveNextRequest() {
|
||||||
|
if (!mResolveRequests.isEmpty()) {
|
||||||
|
PendingResolve request = mResolveRequests.getFirst();
|
||||||
|
mNsdManager.resolveService(request.serviceInfo, request.listener);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
58
src/org/kde/kdeconnect/Helpers/NetworkHelper.kt
Normal file
58
src/org/kde/kdeconnect/Helpers/NetworkHelper.kt
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
/*
|
||||||
|
* SPDX-FileCopyrightText: 2023 Albert Vaca Cintora <albertvaka@gmail.com>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL
|
||||||
|
*/
|
||||||
|
package org.kde.kdeconnect.Helpers
|
||||||
|
|
||||||
|
import java.net.Inet4Address
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.NetworkInterface
|
||||||
|
import java.net.SocketException
|
||||||
|
|
||||||
|
object NetworkHelper {
|
||||||
|
//Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
|
||||||
|
// Anything with rmnet is related to cellular connections or USB
|
||||||
|
// tethering mechanisms. See:
|
||||||
|
//
|
||||||
|
// https://android.googlesource.com/kernel/msm/+/android-msm-flo-3.4-kitkat-mr1/Documentation/usb/gadget_rmnet.txt
|
||||||
|
//
|
||||||
|
// If we run across an interface that has this, we can safely
|
||||||
|
// ignore it. In fact, it's much safer to do. If we don't, we
|
||||||
|
// might get invalid IP adddresses out of it.
|
||||||
|
@JvmStatic
|
||||||
|
val localIpAddress: InetAddress?
|
||||||
|
get() {
|
||||||
|
var ip6: InetAddress? = null
|
||||||
|
try {
|
||||||
|
for (intf in NetworkInterface.getNetworkInterfaces()) {
|
||||||
|
|
||||||
|
// Anything with rmnet is related to cellular connections or USB
|
||||||
|
// tethering mechanisms. See:
|
||||||
|
//
|
||||||
|
// https://android.googlesource.com/kernel/msm/+/android-msm-flo-3.4-kitkat-mr1/Documentation/usb/gadget_rmnet.txt
|
||||||
|
//
|
||||||
|
// If we run across an interface that has this, we can safely
|
||||||
|
// ignore it. In fact, it's much safer to do. If we don't, we
|
||||||
|
// might get invalid IP adddresses out of it.
|
||||||
|
if (intf.displayName.contains("rmnet")) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
val enumIpAddr = intf.inetAddresses
|
||||||
|
while (enumIpAddr.hasMoreElements()) {
|
||||||
|
val inetAddress = enumIpAddr.nextElement()
|
||||||
|
if (!inetAddress.isLoopbackAddress) {
|
||||||
|
ip6 =
|
||||||
|
if (inetAddress is Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
|
||||||
|
return inetAddress
|
||||||
|
} else {
|
||||||
|
inetAddress
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (ignored: SocketException) {
|
||||||
|
}
|
||||||
|
return ip6
|
||||||
|
}
|
||||||
|
}
|
@ -20,6 +20,7 @@ import androidx.annotation.NonNull;
|
|||||||
|
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
|
import org.kde.kdeconnect.Helpers.NetworkHelper;
|
||||||
import org.kde.kdeconnect.NetworkPacket;
|
import org.kde.kdeconnect.NetworkPacket;
|
||||||
import org.kde.kdeconnect.Plugins.Plugin;
|
import org.kde.kdeconnect.Plugins.Plugin;
|
||||||
import org.kde.kdeconnect.Plugins.PluginFactory;
|
import org.kde.kdeconnect.Plugins.PluginFactory;
|
||||||
@ -147,7 +148,7 @@ public class SftpPlugin extends Plugin implements SharedPreferences.OnSharedPref
|
|||||||
|
|
||||||
NetworkPacket np2 = new NetworkPacket(PACKET_TYPE_SFTP);
|
NetworkPacket np2 = new NetworkPacket(PACKET_TYPE_SFTP);
|
||||||
|
|
||||||
np2.set("ip", server.getLocalIpAddress()); // for backwards compatibility
|
np2.set("ip", NetworkHelper.getLocalIpAddress().getHostAddress());
|
||||||
np2.set("port", server.getPort());
|
np2.set("port", server.getPort());
|
||||||
np2.set("user", SimpleSftpServer.USER);
|
np2.set("user", SimpleSftpServer.USER);
|
||||||
np2.set("password", server.getPassword());
|
np2.set("password", server.getPassword());
|
||||||
|
@ -26,17 +26,12 @@ import org.kde.kdeconnect.Helpers.RandomHelper;
|
|||||||
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.Inet4Address;
|
|
||||||
import java.net.InetAddress;
|
|
||||||
import java.net.NetworkInterface;
|
|
||||||
import java.net.SocketException;
|
|
||||||
import java.security.GeneralSecurityException;
|
import java.security.GeneralSecurityException;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.PrivateKey;
|
import java.security.PrivateKey;
|
||||||
import java.security.PublicKey;
|
import java.security.PublicKey;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Enumeration;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
class SimpleSftpServer {
|
class SimpleSftpServer {
|
||||||
@ -145,39 +140,6 @@ class SimpleSftpServer {
|
|||||||
return port;
|
return port;
|
||||||
}
|
}
|
||||||
|
|
||||||
String getLocalIpAddress() {
|
|
||||||
String ip6 = null;
|
|
||||||
try {
|
|
||||||
for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements(); ) {
|
|
||||||
NetworkInterface intf = en.nextElement();
|
|
||||||
|
|
||||||
// Anything with rmnet is related to cellular connections or USB
|
|
||||||
// tethering mechanisms. See:
|
|
||||||
//
|
|
||||||
// https://android.googlesource.com/kernel/msm/+/android-msm-flo-3.4-kitkat-mr1/Documentation/usb/gadget_rmnet.txt
|
|
||||||
//
|
|
||||||
// If we run across an interface that has this, we can safely
|
|
||||||
// ignore it. In fact, it's much safer to do. If we don't, we
|
|
||||||
// might get invalid IP adddresses out of it.
|
|
||||||
if (intf.getDisplayName().contains("rmnet")) continue;
|
|
||||||
|
|
||||||
for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements(); ) {
|
|
||||||
InetAddress inetAddress = enumIpAddr.nextElement();
|
|
||||||
if (!inetAddress.isLoopbackAddress()) {
|
|
||||||
String address = inetAddress.getHostAddress();
|
|
||||||
if (inetAddress instanceof Inet4Address) { //Prefer IPv4 over IPv6, because sshfs doesn't seem to like IPv6
|
|
||||||
return address;
|
|
||||||
} else {
|
|
||||||
ip6 = address;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SocketException ignored) {
|
|
||||||
}
|
|
||||||
return ip6;
|
|
||||||
}
|
|
||||||
|
|
||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return initialized;
|
return initialized;
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ import org.kde.kdeconnect_tp.R;
|
|||||||
|
|
||||||
public class SettingsFragment extends PreferenceFragmentCompat {
|
public class SettingsFragment extends PreferenceFragmentCompat {
|
||||||
|
|
||||||
|
public static final String KEY_UDP_BROADCAST_ENABLED = "udp_broadcast_enabled";
|
||||||
|
public static final String KEY_APP_THEME = "theme_pref";
|
||||||
|
|
||||||
private EditTextPreference renameDevice;
|
private EditTextPreference renameDevice;
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
@ -90,7 +93,7 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
|
|
||||||
// Theme Selector
|
// Theme Selector
|
||||||
ListPreference themeSelector = new ListPreference(context);
|
ListPreference themeSelector = new ListPreference(context);
|
||||||
themeSelector.setKey("theme_pref");
|
themeSelector.setKey(KEY_APP_THEME);
|
||||||
themeSelector.setTitle(R.string.theme_dialog_title);
|
themeSelector.setTitle(R.string.theme_dialog_title);
|
||||||
themeSelector.setDialogTitle(R.string.theme_dialog_title);
|
themeSelector.setDialogTitle(R.string.theme_dialog_title);
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
|
||||||
@ -168,6 +171,12 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
return true;
|
return true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// UDP broadcast toggle
|
||||||
|
final TwoStatePreference udpBroadcastDiscovery = new SwitchPreference(context);
|
||||||
|
udpBroadcastDiscovery.setDefaultValue(true);
|
||||||
|
udpBroadcastDiscovery.setKey(KEY_UDP_BROADCAST_ENABLED);
|
||||||
|
udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast);
|
||||||
|
screen.addPreference(udpBroadcastDiscovery);
|
||||||
|
|
||||||
// More settings text
|
// More settings text
|
||||||
Preference moreSettingsText = new Preference(context);
|
Preference moreSettingsText = new Preference(context);
|
||||||
@ -178,7 +187,5 @@ public class SettingsFragment extends PreferenceFragmentCompat {
|
|||||||
screen.addPreference(moreSettingsText);
|
screen.addPreference(moreSettingsText);
|
||||||
|
|
||||||
setPreferenceScreen(screen);
|
setPreferenceScreen(screen);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user