2
0
mirror of https://github.com/KDE/kdeconnect-android synced 2025-08-22 09:58:08 +00:00

Working bluetooth implementation

This commit is contained in:
Rob Emery 2023-10-26 18:14:59 +01:00
parent 22b6427a18
commit 19ec81eec4
13 changed files with 202 additions and 40 deletions

View File

@ -17,8 +17,10 @@ SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.INTERNET" /> <uses-permission android:name="android.permission.INTERNET" />
<!-- <uses-permission android:name="android.permission.BLUETOOTH" /> --> <uses-permission android:name="android.permission.BLUETOOTH" android:maxSdkVersion="30" />
<!-- <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" /> --> <uses-permission android:name="android.permission.BLUETOOTH_ADMIN" android:maxSdkVersion="30" />
<uses-permission android:name="android.permission.BLUETOOTH_CONNECT" />
<uses-permission android:name="android.permission.BLUETOOTH_SCAN" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" /> <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" /> <uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" /> <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />

View File

@ -55,4 +55,5 @@ public abstract class BaseLinkProvider {
public abstract void onNetworkChange(@Nullable Network network); public abstract void onNetworkChange(@Nullable Network network);
public abstract String getName(); public abstract String getName();
public abstract int getPriority();
} }

View File

@ -189,10 +189,4 @@ public class BluetoothLink extends BaseLink {
return false; return false;
} }
} }
/*
public boolean isConnected() {
return socket.isConnected();
}
*/
} }

View File

@ -14,8 +14,11 @@ import android.content.BroadcastReceiver;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.IntentFilter; import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.net.Network; import android.net.Network;
import android.os.ParcelUuid;
import android.os.Parcelable; import android.os.Parcelable;
import android.preference.PreferenceManager;
import android.util.Base64; import android.util.Base64;
import android.util.Log; import android.util.Log;
@ -29,6 +32,7 @@ import org.kde.kdeconnect.Helpers.DeviceHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Helpers.ThreadHelper; import org.kde.kdeconnect.Helpers.ThreadHelper;
import org.kde.kdeconnect.NetworkPacket; import org.kde.kdeconnect.NetworkPacket;
import org.kde.kdeconnect.UserInterface.SettingsFragment;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
@ -39,6 +43,7 @@ import java.security.cert.Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Random;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@ -58,6 +63,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
private ServerRunnable serverRunnable; private ServerRunnable serverRunnable;
private ClientRunnable clientRunnable; private ClientRunnable clientRunnable;
private Random randomiser;
private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException { private void addLink(NetworkPacket identityPacket, BluetoothLink link) throws CertificateException {
String deviceId = identityPacket.getString("deviceId"); String deviceId = identityPacket.getString("deviceId");
Log.i("BluetoothLinkProvider", "addLink to " + deviceId); Log.i("BluetoothLinkProvider", "addLink to " + deviceId);
@ -66,7 +73,9 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!"); Log.e("BluetoothLinkProvider", "oldLink == link. This should not happen!");
return; return;
} }
synchronized (visibleDevices) {
visibleDevices.put(deviceId, link); visibleDevices.put(deviceId, link);
}
onConnectionReceived(link); onConnectionReceived(link);
link.startListening(); link.startListening();
link.packetReceived(identityPacket); link.packetReceived(identityPacket);
@ -79,6 +88,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
public BluetoothLinkProvider(Context context) { public BluetoothLinkProvider(Context context) {
this.context = context; this.context = context;
this.randomiser = new Random();
bluetoothAdapter = BluetoothAdapter.getDefaultAdapter(); bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
if (bluetoothAdapter == null) { if (bluetoothAdapter == null) {
Log.e("BluetoothLinkProvider", "No bluetooth adapter found."); Log.e("BluetoothLinkProvider", "No bluetooth adapter found.");
@ -87,6 +97,10 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
@Override @Override
public void onStart() { public void onStart() {
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(context);
if (!preferences.getBoolean(SettingsFragment.KEY_BLUETOOTH_ENABLED, false)) {
return;
}
if (bluetoothAdapter == null) { if (bluetoothAdapter == null) {
return; return;
} }
@ -100,6 +114,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
return; return;
} }
Log.i("BluetoothLinkProvider", "onStart called");
//This handles the case when I'm the existing device in the network and receive a hello package //This handles the case when I'm the existing device in the network and receive a hello package
clientRunnable = new ClientRunnable(); clientRunnable = new ClientRunnable();
ThreadHelper.execute(clientRunnable); ThreadHelper.execute(clientRunnable);
@ -111,6 +127,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
@Override @Override
public void onNetworkChange(@Nullable Network network) { public void onNetworkChange(@Nullable Network network) {
Log.i("BluetoothLinkProvider", "onNetworkChange called");
onStop(); onStop();
onStart(); onStart();
} }
@ -120,7 +138,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) { if (bluetoothAdapter == null || clientRunnable == null || serverRunnable == null) {
return; return;
} }
Log.i("BluetoothLinkProvider", "onStop called");
clientRunnable.stopProcessing(); clientRunnable.stopProcessing();
serverRunnable.stopProcessing(); serverRunnable.stopProcessing();
} }
@ -130,9 +148,20 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
return "BluetoothLinkProvider"; return "BluetoothLinkProvider";
} }
@Override
public int getPriority() {
return 10;
}
public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) { public void disconnectedLink(BluetoothLink link, BluetoothDevice remoteAddress) {
Log.i("BluetoothLinkProvider", "disconnectedLink called");
synchronized (sockets) {
sockets.remove(remoteAddress); sockets.remove(remoteAddress);
}
synchronized (visibleDevices) {
visibleDevices.remove(link.getDeviceId()); visibleDevices.remove(link.getDeviceId());
}
onConnectionLost(link); onConnectionLost(link);
} }
@ -158,15 +187,18 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
} catch (IOException e) { } catch (IOException e) {
Log.e("KDEConnect", "Exception", e); Log.e("KDEConnect", "Exception", e);
return; return;
} catch (SecurityException e) {
Log.e("KDEConnect", "Security Exception for CONNECT", e);
return;
} }
while (continueProcessing) {
try { try {
while (continueProcessing) {
BluetoothSocket socket = serverSocket.accept(); BluetoothSocket socket = serverSocket.accept();
connect(socket); connect(socket);
} catch (Exception e) {
Log.e("BTLinkProvider/Server", "Bluetooth error", e);
} }
} catch (Exception e) {
Log.d("BTLinkProvider/Server", "Bluetooth Server error", e);
} }
} }
@ -199,6 +231,7 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context); DeviceInfo myDeviceInfo = DeviceHelper.getDeviceInfo(context);
NetworkPacket np = myDeviceInfo.toIdentityPacket(); NetworkPacket np = myDeviceInfo.toIdentityPacket();
np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0)); np.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
byte[] message = np.serialize().getBytes(Charsets.UTF_8); byte[] message = np.serialize().getBytes(Charsets.UTF_8);
@ -226,29 +259,38 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
Log.i("BTLinkProvider/Server", "Received identity packet"); Log.i("BTLinkProvider/Server", "Received identity packet");
String certificateString = identityPacket.getString("certificate"); String PemEncodedCertificateString = identityPacket.getString("certificate");
byte[] certificateBytes = Base64.decode(certificateString, 0); String base64CertificateString = PemEncodedCertificateString
Certificate certificate = SslHelper.parseCertificate(certificateBytes); .replace("-----BEGIN CERTIFICATE-----\n","")
.replace("-----END CERTIFICATE-----\n","");
byte[] PEMEncodedCertificateBytes = Base64.decode(base64CertificateString, 0);
Certificate certificate = SslHelper.parseCertificate(PEMEncodedCertificateBytes);
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate); DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
Log.i("BTLinkProvider/Server", "About to create link");
BluetoothLink link = new BluetoothLink(context, connection, BluetoothLink link = new BluetoothLink(context, connection,
inputStream, outputStream, socket.getRemoteDevice(), inputStream, outputStream, socket.getRemoteDevice(),
deviceInfo, BluetoothLinkProvider.this); deviceInfo, BluetoothLinkProvider.this);
Log.i("BTLinkProvider/Server", "About to addLink");
addLink(identityPacket, link); addLink(identityPacket, link);
Log.i("BTLinkProvider/Server", "Link Added");
} catch (Exception e) { } catch (Exception e) {
synchronized (sockets) { synchronized (sockets) {
sockets.remove(socket.getRemoteDevice()); sockets.remove(socket.getRemoteDevice());
Log.i("BTLinkProvider/Server", "Exception thrown, removing socket", e);
} }
throw e; throw e;
} }
} }
} }
public static class ClientRunnableSingleton {
private static Map<BluetoothDevice, Thread> connectionThreads = new HashMap<>();
}
private class ClientRunnable extends BroadcastReceiver implements Runnable { private class ClientRunnable extends BroadcastReceiver implements Runnable {
private boolean continueProcessing = true; private boolean continueProcessing = true;
private final Map<BluetoothDevice, Thread> connectionThreads = new HashMap<>();
void stopProcessing() { void stopProcessing() {
continueProcessing = false; continueProcessing = false;
@ -256,31 +298,57 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
@Override @Override
public void run() { public void run() {
Log.i("ClientRunnable", "run called");
IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_UUID); IntentFilter filter = new IntentFilter(BluetoothDevice.ACTION_UUID);
context.registerReceiver(this, filter); context.registerReceiver(this, filter);
Log.i("ClientRunnable", "receiver registered");
if (continueProcessing) { if (continueProcessing) {
connectToDevices(); Log.i("ClientRunnable", "before connectToDevices");
discoverDeviceServices();
Log.i("ClientRunnable", "after connectToDevices");
try { try {
Thread.sleep(15000); Thread.sleep(15000);
} catch (InterruptedException ignored) { } catch (InterruptedException ignored) {
} }
} }
Log.i("ClientRunnable", "unregisteringReceiver");
context.unregisterReceiver(this); context.unregisterReceiver(this);
} }
private void connectToDevices() { /**
* Tell Android to use ServiceDiscoveryProtocol to update the
* list of available UUIDs associated with Bluetooth devices
* that are bluetooth-paired-but-not-yet-kde-paired
*/
private void discoverDeviceServices() {
Log.i("ClientRunnable", "connectToDevices called");
Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices(); Set<BluetoothDevice> pairedDevices = bluetoothAdapter.getBondedDevices();
if(pairedDevices == null) {
return;
}
Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size()); Log.i("BluetoothLinkProvider", "Bluetooth adapter paired devices: " + pairedDevices.size());
// Loop through paired devices // Loop through Bluetooth paired devices
for (BluetoothDevice device : pairedDevices) { for (BluetoothDevice device : pairedDevices) {
// If a socket exists for this, then it has been paired in KDE
if (sockets.containsKey(device)) { if (sockets.containsKey(device)) {
continue; continue;
} }
Log.i("ClientRunnable", "Calling fetchUuidsWithSdp for device: "+device);
device.fetchUuidsWithSdp(); device.fetchUuidsWithSdp();
ParcelUuid[] deviceUuids = device.getUuids();
if(deviceUuids != null){
for(ParcelUuid thisUuid : deviceUuids) {
Log.i("ClientRunnable", "device "+device + " uuid: "+ thisUuid);
}
}
} }
} }
@ -309,14 +377,14 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
} }
private void connectToDevice(BluetoothDevice device) { private void connectToDevice(BluetoothDevice device) {
if (!connectionThreads.containsKey(device) || !connectionThreads.get(device).isAlive()) { synchronized (ClientRunnableSingleton.connectionThreads) {
if (!ClientRunnableSingleton.connectionThreads.containsKey(device) || !ClientRunnableSingleton.connectionThreads.get(device).isAlive()) {
Thread connectionThread = new Thread(new ClientConnect(device)); Thread connectionThread = new Thread(new ClientConnect(device));
connectionThread.start(); connectionThread.start();
connectionThreads.put(device, connectionThread); ClientRunnableSingleton.connectionThreads.put(device, connectionThread);
}
} }
} }
} }
private class ClientConnect implements Runnable { private class ClientConnect implements Runnable {
@ -335,12 +403,21 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
private void connectToDevice() { private void connectToDevice() {
BluetoothSocket socket; BluetoothSocket socket;
try { try {
Log.i("BTLinkProvider/Client", "Cancelling Discovery");
bluetoothAdapter.cancelDiscovery();
Log.i("BTLinkProvider/Client", "Creating RFCommSocket to Service Record");
socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID); socket = device.createRfcommSocketToServiceRecord(SERVICE_UUID);
Log.i("BTLinkProvider/Client", "Connecting to ServiceRecord Socket");
socket.connect(); socket.connect();
synchronized (sockets) {
sockets.put(device, socket); sockets.put(device, socket);
}
} catch (IOException e) { } catch (IOException e) {
Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device.getAddress(), e); Log.e("BTLinkProvider/Client", "Could not connect to KDE Connect service on " + device.getAddress(), e);
return; return;
} catch (SecurityException e){
Log.e("BTLinkProvider/Client", "Security Exception connecting to " + device.getAddress(), e);
return;
} }
Log.i("BTLinkProvider/Client", "Connected to " + device.getAddress()); Log.i("BTLinkProvider/Client", "Connected to " + device.getAddress());
@ -348,19 +425,25 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
try { try {
//Delay to let bluetooth initialize stuff correctly //Delay to let bluetooth initialize stuff correctly
Thread.sleep(500); Thread.sleep(500);
ConnectionMultiplexer connection = new ConnectionMultiplexer(socket); ConnectionMultiplexer connection = new ConnectionMultiplexer(socket);
OutputStream outputStream = connection.getDefaultOutputStream(); OutputStream outputStream = connection.getDefaultOutputStream();
InputStream inputStream = connection.getDefaultInputStream(); InputStream inputStream = connection.getDefaultInputStream();
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" Before inputStream.read()");
int character; int character;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
while (sb.lastIndexOf("\n") == -1 && (character = inputStream.read()) != -1) { while (sb.lastIndexOf("\n") == -1 && (character = inputStream.read()) != -1) {
sb.append((char) character); sb.append((char) character);
} }
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" Before sb.toString()");
String message = sb.toString(); String message = sb.toString();
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" Before unserialize (message: '"+message+"')");
final NetworkPacket identityPacket = NetworkPacket.unserialize(message); final NetworkPacket identityPacket = NetworkPacket.unserialize(message);
Log.i("BTLinkProvider/Client", "Device: " + device.getAddress() +" After unserialize");
if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) { if (!identityPacket.getType().equals(NetworkPacket.PACKET_TYPE_IDENTITY)) {
Log.e("BTLinkProvider/Client", "1 Expecting an identity packet"); Log.e("BTLinkProvider/Client", "1 Expecting an identity packet");
socket.close(); socket.close();
@ -382,9 +465,12 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
Log.i("BTLinkProvider/Client", "identity packet received, creating link"); Log.i("BTLinkProvider/Client", "identity packet received, creating link");
String certificateString = identityPacket.getString("certificate"); String PemEncodedCertificateString = identityPacket.getString("certificate");
byte[] certificateBytes = Base64.decode(certificateString, 0); String base64CertificateString = PemEncodedCertificateString
Certificate certificate = SslHelper.parseCertificate(certificateBytes); .replace("-----BEGIN CERTIFICATE-----\n","")
.replace("-----END CERTIFICATE-----\n","");
byte[] PEMEncodedCertificateBytes = Base64.decode(base64CertificateString, 0);
Certificate certificate = SslHelper.parseCertificate(PEMEncodedCertificateBytes);
DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate); DeviceInfo deviceInfo = DeviceInfo.fromIdentityPacketAndCert(identityPacket, certificate);
final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream, final BluetoothLink link = new BluetoothLink(context, connection, inputStream, outputStream,
@ -394,6 +480,8 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
NetworkPacket np2 = myDeviceInfo.toIdentityPacket(); NetworkPacket np2 = myDeviceInfo.toIdentityPacket();
np2.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0)); np2.set("certificate", Base64.encodeToString(SslHelper.certificate.getEncoded(), 0));
Log.i("BTLinkProvider/Client", "about to send packet np2");
link.sendPacket(np2, new Device.SendPacketStatusCallback() { link.sendPacket(np2, new Device.SendPacketStatusCallback() {
@Override @Override
public void onSuccess() { public void onSuccess() {
@ -409,8 +497,12 @@ public class BluetoothLinkProvider extends BaseLinkProvider {
} }
}, true); }, true);
} catch (Exception e) { } catch (Exception e) {
Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.getAddress(), e); Log.e("BTLinkProvider/Client", "Connection lost/disconnected on " + device.getAddress(), e);
synchronized (sockets) {
sockets.remove(device, socket);
}
} }
} }
} }

View File

@ -7,6 +7,7 @@
package org.kde.kdeconnect.Backends.BluetoothBackend; package org.kde.kdeconnect.Backends.BluetoothBackend;
import android.bluetooth.BluetoothSocket; import android.bluetooth.BluetoothSocket;
import android.util.Log;
import org.kde.kdeconnect.Helpers.ThreadHelper; import org.kde.kdeconnect.Helpers.ThreadHelper;
@ -245,7 +246,7 @@ public final class ConnectionMultiplexer implements Closeable {
channel.doClose(); channel.doClose();
} }
channels.clear(); channels.clear();
if (socket.isConnected()) { if (socket != null && socket.isConnected()) {
try { try {
socket.close(); socket.close();
} catch (IOException ignored) { } catch (IOException ignored) {
@ -438,6 +439,16 @@ public final class ConnectionMultiplexer implements Closeable {
} }
} }
public String byteArrayToHexString(final byte[] bytes) {
StringBuilder sb = new StringBuilder();
for(byte b : bytes){
sb.append(String.format("0x%02x ", b&0xff));
}
return sb.toString();
}
private void read_message() throws IOException { private void read_message() throws IOException {
byte[] data = new byte[BUFFER_SIZE]; byte[] data = new byte[BUFFER_SIZE];
read_buffer(data, 19); read_buffer(data, 19);
@ -451,6 +462,10 @@ public final class ConnectionMultiplexer implements Closeable {
UUID channel_id = new UUID(channel_id_msb, channel_id_lsb); UUID channel_id = new UUID(channel_id_msb, channel_id_lsb);
if (!receivedProtocolVersion && type != MESSAGE_PROTOCOL_VERSION) { if (!receivedProtocolVersion && type != MESSAGE_PROTOCOL_VERSION) {
Log.w("ConnectionMultiplexer", "Received invalid message '" + message+"'");
Log.w("ConnectionMultiplexer", "'data_buffer:("+byteArrayToHexString(data)+") ");
Log.w("ConnectionMultiplexer", "as string: '"+(new String(data, "UTF-8"))+"' ");
throw new IOException("Did not receive protocol version message!"); throw new IOException("Did not receive protocol version message!");
} }
@ -538,11 +553,15 @@ public final class ConnectionMultiplexer implements Closeable {
public void run() { public void run() {
while (true) { while (true) {
synchronized (lock) { synchronized (lock) {
if (!open) return; if (!open) {
Log.w("ConnectionMultiplexer", "connection not open, returning");
return;
}
} }
try { try {
read_message(); read_message();
} catch (IOException e) { } catch (IOException e) {
Log.w("ConnectionMultiplexer", "run caught IOException", e);
handleException(e); handleException(e);
return; return;
} }

View File

@ -494,4 +494,7 @@ public class LanLinkProvider extends BaseLinkProvider {
return "LanLinkProvider"; return "LanLinkProvider";
} }
@Override
public int getPriority() { return 20; }
} }

View File

@ -40,4 +40,7 @@ public class LoopbackLinkProvider extends BaseLinkProvider {
public String getName() { public String getName() {
return "LoopbackLinkProvider"; return "LoopbackLinkProvider";
} }
@Override
public int getPriority() { return 0; }
} }

View File

@ -32,6 +32,7 @@ import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BaseLinkProvider;
import org.kde.kdeconnect.Backends.BluetoothBackend.BluetoothLinkProvider;
import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider; import org.kde.kdeconnect.Backends.LanBackend.LanLinkProvider;
import org.kde.kdeconnect.Helpers.NotificationHelper; import org.kde.kdeconnect.Helpers.NotificationHelper;
import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity; import org.kde.kdeconnect.Plugins.ClibpoardPlugin.ClipboardFloatingActivity;
@ -82,7 +83,7 @@ public class BackgroundService extends Service {
private void registerLinkProviders() { private void registerLinkProviders() {
linkProviders.add(new LanLinkProvider(this)); linkProviders.add(new LanLinkProvider(this));
// linkProviders.add(new LoopbackLinkProvider(this)); // linkProviders.add(new LoopbackLinkProvider(this));
// linkProviders.add(new BluetoothLinkProvider(this)); linkProviders.add(new BluetoothLinkProvider(this));
} }
public void onNetworkChange(@Nullable Network network) { public void onNetworkChange(@Nullable Network network) {

View File

@ -37,7 +37,10 @@ import org.kde.kdeconnect_tp.R;
import java.io.IOException; import java.io.IOException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.CertificateException; import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List; import java.util.List;
import java.util.Vector; import java.util.Vector;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
@ -61,6 +64,7 @@ public class Device implements BaseLink.PacketReceiver {
private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>(); private MultiValuedMap<String, String> pluginsByIncomingInterface = new ArrayListValuedHashMap<>();
private final SharedPreferences settings; private final SharedPreferences settings;
private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>(); private final CopyOnWriteArrayList<PluginsChangedListener> pluginsChangedListeners = new CopyOnWriteArrayList<>();
private String connectivityType;
public boolean supportsPacketType(String type) { public boolean supportsPacketType(String type) {
if (deviceInfo.incomingCapabilities == null) { if (deviceInfo.incomingCapabilities == null) {
@ -70,6 +74,10 @@ public class Device implements BaseLink.PacketReceiver {
} }
} }
public String getConnectivityType() {
return connectivityType;
}
public interface PluginsChangedListener { public interface PluginsChangedListener {
void onPluginsChanged(@NonNull Device device); void onPluginsChanged(@NonNull Device device);
} }
@ -84,6 +92,7 @@ public class Device implements BaseLink.PacketReceiver {
this.deviceInfo = DeviceInfo.loadFromSettings(context, deviceId, settings); this.deviceInfo = DeviceInfo.loadFromSettings(context, deviceId, settings);
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired); this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.Paired);
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
this.connectivityType = "";
Log.i("Device","Loading trusted device: " + deviceInfo.name); Log.i("Device","Loading trusted device: " + deviceInfo.name);
} }
@ -98,6 +107,7 @@ public class Device implements BaseLink.PacketReceiver {
this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE); this.settings = context.getSharedPreferences(deviceInfo.id, Context.MODE_PRIVATE);
this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired); this.pairingHandler = new PairingHandler(this, pairingCallback, PairingHandler.PairState.NotPaired);
this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities this.supportedPlugins = new Vector<>(PluginFactory.getAvailablePlugins()); // Assume all are supported until we receive capabilities
this.connectivityType = link.getLinkProvider().getName();
Log.i("Device","Creating untrusted device: "+ deviceInfo.name); Log.i("Device","Creating untrusted device: "+ deviceInfo.name);
addLink(link); addLink(link);
} }
@ -302,7 +312,14 @@ public class Device implements BaseLink.PacketReceiver {
packetQueue = new DevicePacketQueue(this); packetQueue = new DevicePacketQueue(this);
} }
//FilesHelper.LogOpenFileCount(); //FilesHelper.LogOpenFileCount();
links.add(link); links.add(link);
List linksToSort = Arrays.asList(links.toArray());
Collections.sort(linksToSort, (Comparator<BaseLink>) (o1, o2) -> Integer.compare(o2.getLinkProvider().getPriority(), o1.getLinkProvider().getPriority()));
links.clear();
links.addAll(linksToSort);
link.addPacketReceiver(this); link.addPacketReceiver(this);
boolean hasChanges = updateDeviceInfo(link.getDeviceInfo()); boolean hasChanges = updateDeviceInfo(link.getDeviceInfo());

View File

@ -6,13 +6,17 @@
package org.kde.kdeconnect; package org.kde.kdeconnect;
import android.Manifest;
import android.app.Application; import android.app.Application;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.os.Build; import android.os.Build;
import android.util.Log; import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import org.kde.kdeconnect.Backends.BaseLink; import org.kde.kdeconnect.Backends.BaseLink;
import org.kde.kdeconnect.Backends.BaseLinkProvider; import org.kde.kdeconnect.Backends.BaseLinkProvider;
@ -23,6 +27,7 @@ import org.kde.kdeconnect.Helpers.SecurityHelpers.RsaHelper;
import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper; import org.kde.kdeconnect.Helpers.SecurityHelpers.SslHelper;
import org.kde.kdeconnect.Plugins.Plugin; import org.kde.kdeconnect.Plugins.Plugin;
import org.kde.kdeconnect.Plugins.PluginFactory; import org.kde.kdeconnect.Plugins.PluginFactory;
import org.kde.kdeconnect.UserInterface.MainActivity;
import org.kde.kdeconnect.UserInterface.ThemeUtil; import org.kde.kdeconnect.UserInterface.ThemeUtil;
import org.kde.kdeconnect_tp.BuildConfig; import org.kde.kdeconnect_tp.BuildConfig;
import org.slf4j.impl.HandroidLoggerAdapter; import org.slf4j.impl.HandroidLoggerAdapter;
@ -62,6 +67,7 @@ public class KdeConnect extends Application {
NotificationHelper.initializeChannels(this); NotificationHelper.initializeChannels(this);
LifecycleHelper.initializeObserver(); LifecycleHelper.initializeObserver();
loadRememberedDevicesFromSettings(); loadRememberedDevicesFromSettings();
} }
private void setupSL4JLogging() { private void setupSL4JLogging() {

View File

@ -39,6 +39,7 @@ public class PairingDeviceItem implements ListAdapter.Item {
final ListItemWithIconEntryBinding binding = ListItemWithIconEntryBinding.inflate(layoutInflater); final ListItemWithIconEntryBinding binding = ListItemWithIconEntryBinding.inflate(layoutInflater);
binding.listItemEntryIcon.setImageDrawable(device.getIcon()); binding.listItemEntryIcon.setImageDrawable(device.getIcon());
// binding.listItemEntryTitle.setText(device.getName() + " " + device.getConnectivityType());
binding.listItemEntryTitle.setText(device.getName()); binding.listItemEntryTitle.setText(device.getName());
if (device.compareProtocolVersion() != 0) { if (device.compareProtocolVersion() != 0) {

View File

@ -39,6 +39,7 @@ import org.kde.kdeconnect.UserInterface.About.AboutFragment
import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData import org.kde.kdeconnect.UserInterface.About.getApplicationAboutData
import org.kde.kdeconnect_tp.R import org.kde.kdeconnect_tp.R
import org.kde.kdeconnect_tp.databinding.ActivityMainBinding import org.kde.kdeconnect_tp.databinding.ActivityMainBinding
import java.util.LinkedList
private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection private const val MENU_ENTRY_ADD_DEVICE = 1 //0 means no-selection
private const val MENU_ENTRY_SETTINGS = 2 private const val MENU_ENTRY_SETTINGS = 2
@ -195,14 +196,20 @@ class MainActivity : AppCompatActivity(), OnSharedPreferenceChangeListener {
} }
} }
val missingPermissions = mutableListOf<String>()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
val permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS) val permissionResult = ContextCompat.checkSelfPermission(this, Manifest.permission.POST_NOTIFICATIONS)
if (permissionResult != PackageManager.PERMISSION_GRANTED) { if (permissionResult != PackageManager.PERMISSION_GRANTED) {
if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) { if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.POST_NOTIFICATIONS)) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.POST_NOTIFICATIONS), RESULT_NOTIFICATIONS_ENABLED) missingPermissions.add(Manifest.permission.POST_NOTIFICATIONS)
} }
} }
} }
if(missingPermissions.size > 0){
ActivityCompat.requestPermissions(this, missingPermissions.toTypedArray(), RESULT_NOTIFICATIONS_ENABLED)
}
} }
override fun onDestroy() { override fun onDestroy() {

View File

@ -6,6 +6,8 @@
package org.kde.kdeconnect.UserInterface; package org.kde.kdeconnect.UserInterface;
import android.Manifest;
import android.app.Activity;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.Configuration; import android.content.res.Configuration;
@ -20,6 +22,7 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.core.app.ActivityCompat;
import androidx.preference.EditTextPreference; import androidx.preference.EditTextPreference;
import androidx.preference.ListPreference; import androidx.preference.ListPreference;
import androidx.preference.Preference; import androidx.preference.Preference;
@ -38,6 +41,7 @@ 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_UDP_BROADCAST_ENABLED = "udp_broadcast_enabled";
public static final String KEY_BLUETOOTH_ENABLED = "bluetooth_enabled";
public static final String KEY_APP_THEME = "theme_pref"; public static final String KEY_APP_THEME = "theme_pref";
private EditTextPreference renameDevice; private EditTextPreference renameDevice;
@ -178,6 +182,18 @@ public class SettingsFragment extends PreferenceFragmentCompat {
udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast); udpBroadcastDiscovery.setTitle(R.string.enable_udp_broadcast);
screen.addPreference(udpBroadcastDiscovery); screen.addPreference(udpBroadcastDiscovery);
final TwoStatePreference enableBluetoothSupport = new SwitchPreference(context);
enableBluetoothSupport.setDefaultValue(false);
enableBluetoothSupport.setKey(KEY_BLUETOOTH_ENABLED);
enableBluetoothSupport.setTitle("Enable bluetooth (beta)");
enableBluetoothSupport.setOnPreferenceChangeListener((preference, newValue) -> {
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.S && (boolean)newValue) {
ActivityCompat.requestPermissions(getActivity(), new String[]{Manifest.permission.BLUETOOTH_CONNECT, Manifest.permission.BLUETOOTH_SCAN}, 2);
}
return true;
});
screen.addPreference(enableBluetoothSupport);
// More settings text // More settings text
Preference moreSettingsText = new Preference(context); Preference moreSettingsText = new Preference(context);
moreSettingsText.setPersistent(false); moreSettingsText.setPersistent(false);